Harsh Pandya
Written By Harsh Pandya Android - Team Lead

How to use Retrofit 2.0 with Kotlin and Kodein using MVVM Architecture?

post date
May 30, 2022
Reading Time: 3 minutes

What is MVVM?

The Model-View-ViewModel (MVVM) pattern is a software design pattern that separates programme logic from user interface controls. Microsoft architects Ken Cooper and John Gossman created MVVM, also known as model-view-binder.

What is Kotlin?

Kotlin is a programming language. Nowadays, Kotlin is widely used to write code for Android Applications. Kotlin is easy to learn and understandable. In comparison with Java, Kotlin is preferable because it is Easy-to-use, has better performance, and has better scalability.

What is Retrofit 2.0?

Retrofit 2.0 is a type-safe HTTP client for Android built by Square and maintained by Google. Programmers can use it to call APIs (web services) to communicate with servers. Retrofit is a widely used library. Retrofit is the most efficient network request library, offers better functionality, and has a simpler syntax.

What is Kodein?

KOtlin DEpendency INjection is a simple Kotlin dependency retrieval container. It is easy to use and configure, small, fast and optimized. Programmers are allowed to lazily instantiate dependencies when needed. It can easily bind classes or interfaces to their instance, provider or factory.

Let’s check the example now:

We will use Cat-API for this demo project. You can find documentation and other required details at https://thecatapi.com/

API: https://api.thecatapi.com/v1/breeds

Step 1: Add the following dependencies to your app-level Gradle

dependencies {
//Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

// ViewModel, LifeCycle and LiveData
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

//Kodein
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
}

 

Step 2: Add internet permission to Android Manifest.xml

<uses-permission android:name="android.permission.INTERNET" />

 

Step 3: Add BreedResponse.kt data class.

class BreedResponse: ArrayList < BreedResponse.BreedResponseItem > () 
{
   data class BreedResponseItem( 
    val adaptability: Int, 
    val affection_level: Int,
    val alt_names: String,
    val bidability: Int,
    val cat_friendly: Int,
    val cfa_url: String,
    val child_friendly: Int,
    val country_code: String,
    val country_codes: String,
    val description: String,
    val dog_friendly: Int,
    val energy_level: Int,
    val experimental: Int,
    val grooming: Int,
    val hairless: Int,
    val health_issues: Int,
    val hypoallergenic: Int,
    val id: String,
    val image: Image ? ,
    val indoor : Int,
    val intelligence: Int,
    val lap: Int,
    val life_span: String,
    val name: String,
    val natural: Int,
    val origin: String,
    val rare: Int,
    val reference_image_id: String,
    val rex: Int,
    val shedding_level: Int,
    val short_legs: Int,
    val social_needs: Int,
    val stranger_friendly: Int,
    val suppressed_tail: Int,
    val temperament: String,
    val vcahospitals_url: String,
    val vetstreet_url: String,
    val vocalisation: Int,
    val weight: Weight,
    val wikipedia_url: String
  ): Serializable

  data class Image(
    val height: Int,
    val id: String,
    val url: String ? ,
    val width : Int
  ): Serializable

  data class Weight(
    val imperial: String,
    val metric: String
  ): Serializable
}

 

Step 4: Add API client for retrofit builder

object MyBase 
{
  private
  var retrofit: Retrofit ? = null

  fun getMyBase(context: Context ? ): Retrofit ? 
    {
    if (retrofit == null) 
     {
      retrofit = Retrofit.Builder()
      .baseUrl(Constants.ApiValues.BASE_URL)
      .addConverterFactory(GsonConverterFactory.create())
      .build()
    }
    return retrofit
  }
}

 

Step 5: Add API Service interface

interface MyCalls {
  @GET(Constants.ApiValues.BREEDS)
  suspend fun getBreedsCall(
    @Query("page") page: String?,
    @Query("limit") limit: String?,
  ): Response<BreedResponse>
}

 

Step 6: Create Util to select API

fun selectApi(): MyCalls? {
  return MyBase.getMyBase()?.create(MyCalls::class.java)
}

 

Step 7: Create a SafeApiRequest class

abstract class SafeApiRequest {
  suspend fun < T: Any > apiRequest(call: suspend() -> Response < T > ? ): T ? {
    val response = call.invoke()
    return
      if (response != null) {
        if (response.isSuccessful) {
          response.body()
        } else {
          return null
        }
      } else {
        null
      }
  }
}

 

Step 8: Create Application Class to build instance using Kodein

class ApplicationClass: Application(), KodeinAware {
  override val kodein = Kodein.lazy {
    import(androidXModule(this @ * YOUR_APPLICATION * ))

    bind() from singleton {
      MainActivityRepository(instance())
    }
    bind() from provider {
      MainActivityViewModelFactory(instance())
    }
  }
}

 

Step 9: Create Factory, Repository, Coroutines and ViewModel

//Factory
class MainActivityViewModelFactory(
  private val repository: MainActivityRepository
): ViewModelProvider.NewInstanceFactory() {
  override fun < T: ViewModel > create(modelClass: Class < T > ): T {
    return MainActivityViewModel(repository) as T
  }
}

//Repository
class MainActivityRepository(
  private val context: Context
): SafeApiRequest() {
  suspend fun getBreedsCall(pageCount: String, listLimit: String) = apiRequest {
    selectApi()?.getBreedsCall(pageCount, listLimit)
  }
}

//Coroutines
object Coroutines {
  fun < T: Any > ioThenMain(work: suspend(() -> T ? ), callback: ((T ? ) -> Unit)) =
    CoroutineScope(Dispatchers.Main).launch {
      val data = CoroutineScope(Dispatchers.IO).async rt @ {
        return @rt work()
      }.await()
      callback(data)
    }
}

//ViewModel
class MainActivityViewModel(
  private val repository: MainActivityRepository
): ViewModel() {

  private lateinit
  var job: Job

  private val _getBreedsResponse = MutableLiveData < BreedResponse ? > ()
  val getBreedsResponse: LiveData < BreedResponse ? >
    get() = _getBreedsResponse

  fun getBreeds(pageCount: String, listLimit: String) {
    job = Coroutines.ioThenMain({
      repository.getBreedsCall(pageCount, listLimit)
    }, {
      _getBreedsResponse.value = it
    })
  }
}

 

Step 10: (Final): Call API and Observ Response in Activity or Fragment

class MainActivity: AppCompatActivity(), KodeinAware {

  //....other code(such as pagination and all)....
  //Observe the Response
  viewModel?.getBreedsResponse?.observe(this) {
    viewGone(binding?.llProgress)
    if (it != null) {
      setBreedData(it)
      intPageCount += 1
    } else {
      Log.d(TAG, "init: $it")
    }
  }
}

//Call the API
private fun retrofitGetBreedsCall() {
  viewVisible(binding?.llProgress)
  viewModel?.getBreeds(
    intPageCount.toString(),
    intListLimit.toString()
  )
}
//...other Code...

According to the layout and Recyclerview Design you can set the data from the observer.

Harsh Pandya
Written By Harsh Pandya Android - Team Lead

Popular Categories

Get In Touch

Contact
GET IN TOUCH

Consult With Our Experts Today!

Your Benefits!

  • Professional Project Consultation
  • Detailed Project Proposal
Skype WhatsApp