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.
