Retrofit With MVVM And Repository Pattern.

Harshita Bambure
4 min readApr 15, 2021

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,

Resources

https://jsonplaceholder.typicode.com/comments

Raw data of comments

jsonschema2pojo

Generate Plain Old Java Objects from JSON or JSON-Schema.

Click on Preview

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!

--

--

Harshita Bambure

Android Developer || WomenTech Global Ambassador at WomenTech Network. || Yoga Teacher || Member @WTM .