Jetpack Compose App with MVVM Coroutines

In this article, we are going to fetch data from API by using MVVM Architecture with Retrofit library and Kotlin Coroutines in Android Jetpack Compose App.

MVVM means Model-View-ViewModel. MVVM Architecture is used to separate data from business logic in applications. In this flutter example, we are integrating UI with jetpack compose components and fetch data using MVVM (ViewModel & LiveData) with Retrofit and Coroutines.

 

Add required libraries in app level build.gradle file

// lifecycle
implementation ‘androidx.lifecycle:lifecycle-common:2.4.0’
implementation ‘androidx.lifecycle:lifecycle-runtime:2.4.0’
implementation ‘androidx.lifecycle:lifecycle-livedata-ktx:2.4.0’
// retrofit
implementation ‘com.squareup.retrofit2:retrofit:2.6.0’
implementation ‘com.squareup.retrofit2:converter-gson:2.5.0’
// coroutines
implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2’
 
 

Create Api Service to get Data from Webservice

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import retrofit2.http.GET
import java.util.ArrayList

interface MyApiService {

@GET(“users”)
suspend fun getEmployees(): ArrayList<Employee>

}

 
 

Create Api Client Class to use Retrofit Library

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object MyApiClient {

private const val BASE_URL: String = “https://jsonplaceholder.typicode.com/”

private val gson : Gson by lazy {
GsonBuilder().setLenient().create()
}

private val httpClient : OkHttpClient by lazy {
OkHttpClient.Builder().build()
}

private val retrofit : Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}

val MY_API_SERVICE : MyApiService by lazy{
retrofit.create(MyApiService::class.java)
}

}

 
 

Create Api Response Class to detect State of Api Call using Kotlin Couroutines

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

data class MyApiResponse<out T>(val myApiStatus: MyApiStatus, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T): MyApiResponse<T> = MyApiResponse(myApiStatus = MyApiStatus.SUCCESS, data = data, message = null)
fun <T> error(data: T?, message: String): MyApiResponse<T> =
MyApiResponse(myApiStatus = MyApiStatus.ERROR, data = data, message = message)

fun <T> loading(data: T?): MyApiResponse<T> = MyApiResponse(myApiStatus = MyApiStatus.LOADING, data = data, message = null)
}
}

enum class MyApiStatus {
SUCCESS,
ERROR,
LOADING
}

 
 

Create Model Classes to handle data for MVVM Retrofit Kotlin

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import com.google.gson.annotations.SerializedName
import java.io.Serializable

data class Employee(

@SerializedName(“id”)
val empId: Int? = null,

@SerializedName(“name”)
val empName: String? = null,

@SerializedName(“username”)
val empUserName: String? = null,

@SerializedName(“email”)
val empEmail: String? = null,

@SerializedName(“address”)
val empAddressObject: EmployeeAddress? = null,

@SerializedName(“phone”)
val empPhone: String? = null,

@SerializedName(“website”)
val empWebsite: String? = null
) : Serializable

 
package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import com.google.gson.annotations.SerializedName
import java.io.Serializable

data class EmployeeAddress(
@SerializedName(“street”)
val empStreet: String? = null,

@SerializedName(“suite”)
val empSuite: String? = null,

@SerializedName(“city”)
val empCity: String? = null,

@SerializedName(“zipcode”)
val empZipCode: String? = null

) : Serializable

 
 

Android Jetpack Compose App RecyclerView

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable

@Composable
fun EmployeesList(listEmployees: MutableList<Employee>){

LazyColumn(){
items(
items = listEmployees,
itemContent = { EmployeeListItem(employee = it) }
)
}

}

 
 

List View Row for Jetpack Compose App Layout

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun EmployeeListItem(employee: Employee){
Card(
modifier = Modifier.padding(horizontal = 15.dp, vertical = 12.dp).fillMaxWidth(),
shape = RoundedCornerShape(corner = CornerSize(15.dp)),
elevation = 5.dp
) {
val empAddressObject = remember {
employee.empAddressObject
}
Column(
modifier = Modifier.padding(all = 15.dp)
) {
employee.empName?.let { Text(it, fontSize = 22.sp) }
Text(employee.empUserName + ” | ” + employee.empEmail)
Text(employee.empPhone + ” | ” + employee.empWebsite)
Text(empAddressObject?.empSuite + “,” + empAddressObject?.empStreet + “,” + empAddressObject?.empCity + “,” + empAddressObject?.empZipCode)
}

}
}

 
 

Create ViewModel Class in Jetpack Compose App

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import kotlinx.coroutines.Dispatchers

class EmployeeViewModel() : ViewModel() {
fun getEmployeesData() = liveData(Dispatchers.IO) {
emit(MyApiResponse.loading(data = null))
try {
emit(MyApiResponse.success(data = MyApiClient.MY_API_SERVICE.getEmployees()))
} catch (exception: Exception) {
emit(MyApiResponse.error(data = null, message = exception.message ?: “Error Occurred!”))
}
}

}

 
 

Android Jetpack Compose App Main Activity

package com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.codingwithdhrumil.jetpackcomposemvvmretrofitcoroutinessample.ui.theme.JetpackComposeMVVMRetrofitCoroutinesSampleTheme

class MainActivity : ComponentActivity() {

private var getListEmployees: MutableList<Employee> by mutableStateOf(mutableListOf())
private var isLoading: Boolean by mutableStateOf(false)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposeMVVMRetrofitCoroutinesSampleTheme {
Surface(color = MaterialTheme.colors.background) {
MainActivityLayout(getEmployeesList = getListEmployees, isLoading = isLoading)
}
}
}
val employeeViewModel = ViewModelProvider(this).get(EmployeeViewModel::class.java)
employeeViewModel.getEmployeesData().observe(this, Observer {
it?.let { myApiResponse ->
when (myApiResponse.myApiStatus) {
MyApiStatus.SUCCESS -> {
myApiResponse.data?.let {
getListEmployees.clear()
getListEmployees.addAll(it)
isLoading = false
}
}
MyApiStatus.ERROR -> {
isLoading = false
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
MyApiStatus.LOADING -> {
isLoading = true
}
}
}
})
}
}

@Composable
fun MainActivityLayout(getEmployeesList : MutableList<Employee>, isLoading: Boolean) {
if(isLoading){
Box(contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator()
}
} else {
EmployeesList(listEmployees = getEmployeesList)
}
}

 
 
 
jetpack compose app
 

Leave a Reply