Retrofit With MVVM And Repository Pattern.
Today, we will learn a new way to work with Retrofit Library.
first of all, we need to go to the JSON placeholder
then select any one of the resources for example,
https://jsonplaceholder.typicode.com/comments
jsonschema2pojo
Click on Preview
go to the android studio and add internet permission in the manifest file.
<uses-permission android:name="android.permission.INTERNET"/>
add dependencies in the build.gradle(app) file.
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//kotlin coroutine
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
//RetroFit Dependencies
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.3.1'
//Lifecycle
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common:2.3.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
recycler_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/tv_postid"
tools:text="post id"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_id"
tools:text="user id"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_name"
tools:text="user name"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_email"
tools:text="user email"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_body"
tools:text="user body"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
All the code you need in your comment. kt, add preview data here.
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
class Comment {
@SerializedName("postId")
@Expose
var postId: Int? = null
@SerializedName("id")
@Expose
var id: Int? = null
@SerializedName("name")
@Expose
var name: String? = null
@SerializedName("email")
@Expose
var email: String? = null
@SerializedName("body")
@Expose
var body: String? = null
}
Constant Object.kt
object Constants {
const val BASE_URL = "https://jsonplaceholder.typicode.com/"
val COMMENT = "comments"
}
Api Interface.kt
import retrofit2.http.GET
interface ApiInterface {
@GET("comments")
suspend fun getAllComments():List<Comment>
}
Api Helper.kt
class ApiHelper (private val apiInterface: ApiInterface) {
suspend fun getAllComments() = apiInterface.getAllComments()
}
Object RetrofitClientCalling.kt
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClientCalling {
var mHttpLoggingInterceptor = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)
var mOkHttpClient = OkHttpClient
.Builder()
.addInterceptor(mHttpLoggingInterceptor)
.build()
var mRetrofit: Retrofit? = null
val client: Retrofit?
get() {
if(mRetrofit == null){
mRetrofit = Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.client(mOkHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return mRetrofit
}
}
AppCreator.kt
import android.app.Application
import com.example.retrofit.RetrofitClientCalling.client
class AppCreator : Application() {
companion object {
private var mApiHelper:ApiHelper? = null
fun getApiHelperInstance():ApiHelper{
if(mApiHelper==null){
mApiHelper = ApiHelper(client!!.create(ApiInterface::class.java))
}
return mApiHelper!!
}
}
}
Comment Repo.kt
class CommentRepo(private val apiHelper: ApiHelper) {
suspend fun getAllComments() = apiHelper.getAllComments()
}
Status.kt
enum class Status {
SUCCESS,
FAILURE,
LOADING
}
Resource.kt
data class Resource<out T>
(val status: Status, val data:T?, val message:String?){
companion object{
fun <T> success(data:T): Resource<T> =
Resource(status = Status.SUCCESS, data = data, message = null)
fun <T> error(data:T?, message: String?):Resource<T> =
Resource(status = Status.FAILURE, data = data, message = message)
fun <T> loading(data:T?):Resource<T> =
Resource(status = Status.LOADING, data = data, message = null)
}
}
CommentViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
class CommentViewModel(private val mCommentRepo: CommentRepo) : ViewModel(){
fun getAllComments() = liveData {
emit(Resource.loading(null))
try{
emit(Resource.success(mCommentRepo.getAllComments()))
} catch (e:Exception){
emit(Resource.error(null,e.message.toString()))
}
}
}
ViewModelFactory.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import java.lang.IllegalArgumentException
@Suppress("UNCHECKED_CAST")
class ViewModelFactory(private val apiHelper: ApiHelper) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if(modelClass.isAssignableFrom(CommentViewModel::class.java)){
return CommentViewModel(CommentRepo(apiHelper)) as T
}
throw IllegalArgumentException("Class not found")
}
}
Comment Recycler Adapter Class
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class CommentRecyclerAdapter
: RecyclerView.Adapter<CommentRecyclerAdapter.ViewHolder>() {
private val mCommentList = ArrayList<Comment>()
//view holder class which binds the view with respective data that came to adaper.
class ViewHolder(itemView: View)
: RecyclerView.ViewHolder(itemView) {
private val mTvPostId : TextView
private val mTvId : TextView
private val mTvName : TextView
private val mTvEmail : TextView
private val mTvBody : TextView
init {
mTvPostId = itemView.findViewById(R.id.tv_postid)
mTvId = itemView.findViewById(R.id.tv_id)
mTvName = itemView.findViewById(R.id.tv_name)
mTvEmail = itemView.findViewById(R.id.tv_email)
mTvBody = itemView.findViewById(R.id.tv_body)
}
fun bind(comment: Comment){
mTvPostId.text = comment.postId.toString()
mTvId.text = comment.id.toString()
mTvName.text = comment.name
mTvEmail.text = comment.email
mTvBody.text = comment.body
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater
.from(parent.context)
.inflate(R.layout.recycler_item_layout,parent,false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(mCommentList[position])
}
override fun getItemCount(): Int {
return mCommentList.size
}
fun updateRecyclerData(postList:List<Comment>){
mCommentList.clear()
mCommentList.addAll(postList)
notifyDataSetChanged()
}
}
Main Activity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.observe
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var mCommentViewModel:CommentViewModel
private lateinit var mRecyclerAdapter: CommentRecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initData()
setRecycler()
obtainListFromServer()
}
//constantly observing the data came from the API service, also listening to the status
//of the end result as can see through the when cases.
private fun obtainListFromServer() {
mCommentViewModel.getAllComments().observe(this) {
when(it.status){
Status.SUCCESS -> {
mRecyclerAdapter.updateRecyclerData(it.data!!)
}
Status.FAILURE -> {
Toast.makeText(
this,
"Failed to load the data ${it.message}",
Toast.LENGTH_LONG
).show()
}
Status.LOADING -> {
Toast.makeText(
this,
"Loading...",
Toast.LENGTH_LONG
).show()
}
}
}
}
private fun setRecycler() {
rv.layoutManager = LinearLayoutManager(this)
rv.setHasFixedSize(false)
rv.adapter = mRecyclerAdapter
}
private fun initData() {
//initialization of viewmodel instance,
mCommentViewModel = ViewModelProvider(
this,
ViewModelFactory(AppCreator.getApiHelperInstance())
).get(CommentViewModel::class.java)
mRecyclerAdapter = CommentRecyclerAdapter()
}
}
That’s it!