Jetpack Compose Mvvm Retrofit Example Step by Step in 2024

In this blog I am gonna show you how to setup dagger hilt in Jetpack Compose using Mvvm Retrofit with Example step by step in 2024 and also i will explain you how setup proper project structure in kotlin jetpack compose.

Jetpack Compose Mvvm Retrofit Example

Jetpack Compose Mvvm Retrofit Example Step by Step in 2024

Step 1 : Add Below Snippet Dependency in your app level :- build.gradle.kts (Module :app)

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
//--->New code<---👇
id("dagger.hilt.android.plugin")
//--->New code<---👆
}

android {
namespace = "com.example.myjetpackcomposeapp"
compileSdk = 34

defaultConfig {
applicationId = "com.example.myjetpackcomposeapp"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}

dependencies {

implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))

//--->New code<---👇
//Navigation
implementation("androidx.lifecycle:lifecycle-viewmodel-compose")
implementation("androidx.navigation:navigation-compose:2.7.6")
implementation("com.google.accompanist:accompanist-navigation-animation:0.30.1")

// Dependency Injection
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-android-compiler:2.48")
implementation("androidx.hilt:hilt-work:1.1.0")
kapt("androidx.hilt:hilt-compiler:1.1.0")
implementation("androidx.work:work-runtime-ktx:2.9.0")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")

// Lifecycle
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")

// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")

// Coroutine Lifecycle Scopes
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")

// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.2")
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2")

// Retrofit2
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")

// Coil
implementation("io.coil-kt:coil-compose:1.4.0")

// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
//--->New code<---👆

//Junit
testImplementation("io.mockk:mockk:1.13.7")
testImplementation("io.mockk:mockk-android:1.13.7")
testImplementation("io.mockk:mockk-agent:1.13.7")
androidTestImplementation("io.mockk:mockk-android:1.13.7")
androidTestImplementation("io.mockk:mockk-agent:1.13.7")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
testImplementation("ch.qos.logback:logback-classic:1.2.3")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2")
testImplementation("androidx.arch.core:core-testing:2.2.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}


Step 2 : Add Below Snippet Dependency in your project level :- build.gradle.kts (Project)

plugins {
id("com.android.application") version "8.2.0" apply false
id("com.android.library") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
//--->New code<---👇
id("com.google.dagger.hilt.android") version "2.48" apply false
//--->New code<---👆
}


Step 3 : Add Internet Permission in your Manifest File

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


Step 4 : Create new Kotlin class for Application Class (Base Class) that is use for annotated of Dagger Hilt.


import dagger.hilt.android.HiltAndroidApp
import android.app.Application

@HiltAndroidApp
class ApplicationClass: Application()



Step 5 : Use Application class in your Manifest file like below Snippet.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:name=".ApplicationClass"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.MyJetpackComposeApp"
tools:targetApi="31">
<activity
android:name=".screens.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyJetpackComposeApp">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


Step 6 : Create Application Structure like below image.

Jetpack Compose Mvvm Retrofit Example

Step 7 : Create Constant File for adding Base URL - Jetpack Compose MVVM API.

object Constant {
const val BaseUrl = "https://gorest.co.in/public/v2/"
}


Step 8 : Create Api Services Interface File for adding multiple end point of URL.

import com.example.myjetpackcomposeapp.data.model.HighSchoolModelItem
import retrofit2.http.GET

interface ApiService {
@GET("users")
suspend fun getHighSchool(): List<HighSchoolModelItem>
}


Step 9 : Create Data Class for receiving data from server side.

data class HighSchoolModelItem(
val email: String,
val gender: String,
val id: Int,
val name: String,
val status: String
)


Step 10 : Create Api State File for handle the multiple type of server response.

sealed class ApiState<out T: Any> {
data object Empty : ApiState<Nothing>()
data object Requesting : ApiState<Nothing>()
data class Success<out T : Any>(val ret:T) : ApiState<T>()
data class Error(val responseCode: Int,val errorResponse:String) : ApiState<Nothing>()
data class Exception(val e: Throwable) : ApiState<Nothing>(){
val errorCode = 999
}
}


Step 11 : Create Network Module File for contributes to the object graph in DI and also help to connect base url and end point of url.

import com.example.myjetpackcomposeapp.utils.Constant
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {

@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.readTimeout(2, TimeUnit.MINUTES)
.connectTimeout(2, TimeUnit.MINUTES)
.build()
}

@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(Constant.BaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}


Step 12 : Create Shared View Model class for implementing all business logic - Jetpack Compose ViewModel.

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myjetpackcomposeapp.data.model.HighSchoolModelItem
import com.example.myjetpackcomposeapp.data.network.ApiService
import com.example.myjetpackcomposeapp.data.network.ApiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class HighSchoolViewModel @Inject constructor(private val apiService: ApiService) : ViewModel() {
private val _highSchool = MutableStateFlow<ApiState<List<HighSchoolModelItem>>>(ApiState.Requesting)
val highSchoolState = _highSchool.asStateFlow()
private val _schoolItem = MutableStateFlow<HighSchoolModelItem?>(null)
val schoolItem = _schoolItem.asStateFlow()

init {
fetchHighSchool()
}

fun fetchHighSchool() {
viewModelScope.launch {
try {
val response = apiService.getHighSchool()
_highSchool.value = ApiState.Success(response)
} catch (e: Exception) {
_highSchool.value = ApiState.Error(401,e.message.toString())
}
}
}
fun selectedHighSchoolItem(schoolItem: HighSchoolModelItem){
_schoolItem.value = schoolItem
}
}


Step 13 : Create First Screen for showing all school list.

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import com.example.myjetpackcomposeapp.data.network.ApiState
import com.example.myjetpackcomposeapp.nav.Routes.MainRoute.SchoolDetails.toSchoolDetails

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HighSchoolListScreen(
navController: NavController,
viewModel: HighSchoolViewModel = hiltViewModel()
) {
val highSchoolState by viewModel.highSchoolState.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "High School List")
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
Icons.Filled.ArrowBack, "backIcon",
tint = Color.White
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = Color.White,
)
)
}) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
) {
when (highSchoolState) {
is ApiState.Requesting -> {
// Show loading indicator
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
}
}

is ApiState.Success -> {
val schoolList = (highSchoolState as ApiState.Success).ret
// Display the fetched data using Jetpack Compose components
Column {
Spacer(modifier = Modifier.height(20.dp))
// Text("User List:")
LazyColumn {
itemsIndexed(schoolList) { _, item ->
HighSchoolList(
item.name,
item.email,
onClick = {
viewModel.selectedHighSchoolItem(item)
navController.toSchoolDetails()
})
}
}

}
}

is ApiState.Error -> {
val error = (highSchoolState as ApiState.Error).errorResponse
// Show error message
Column {
Text(
text = error,
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.align(Alignment.CenterHorizontally)
)
}
}

else -> {}
}
}
}

}


@Composable
fun HighSchoolList(
schoolName: String,
dbn: String,
onClick: () -> Unit
) {
Box(modifier = Modifier.clickable { onClick() }) {

Column(
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth()
) {
Text(text = schoolName, style = typography.bodyLarge, color = Color.Black)
Spacer(modifier = Modifier.height(5.dp))
Text(text = dbn, style = typography.bodyMedium, color = Color.Gray)
Divider(modifier = Modifier.padding(vertical = 10.dp), color = Color.LightGray)
}
}
}

Step 14 : Create Second Screen for showing School Details.

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HighSchoolDetailsScreen(
navController: NavHostController,
viewModel: HighSchoolViewModel = hiltViewModel()
) {
val schoolItem by viewModel.schoolItem.collectAsState()

Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "High School Details")
},
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
Icons.Filled.ArrowBack, "backIcon",
tint = Color.White
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = Color.White,
)
)
}) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.padding(horizontal = 10.dp)
.fillMaxWidth()
)
{
Spacer(modifier = Modifier.height(5.dp))
Text(
text = schoolItem?.name.toString(),
style = MaterialTheme.typography.headlineMedium,
color = Color.Black,
modifier = Modifier.align(alignment = androidx.compose.ui.Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(5.dp))
Text(
text = "Dbn - ${schoolItem?.email}",
style = MaterialTheme.typography.bodyMedium,
color = Color.Gray
)
Spacer(modifier = Modifier.height(5.dp))
Text(
text = "Overview Paragraph - ${schoolItem?.gender}",
style = MaterialTheme.typography.bodyMedium,
color = Color.Gray
)
Spacer(modifier = Modifier.height(5.dp))
}

}

}


Step 15 : Create Routes for navigation between screens.

import androidx.navigation.NavController

sealed class Routes(val route: String) {

data object MainRoute : Routes("mainRoutes") {

data object SchoolList : Routes("${MainRoute.route}/schoolList") {
fun NavController.toSchoolList() = navigate("${MainRoute.route}/schoolList")
}

data object SchoolDetails : Routes("${MainRoute.route}/schoolDetails") {
fun NavController.toSchoolDetails() = navigate("${MainRoute.route}/schoolDetails")

}
}

}


Step 16 : Create MainNavigation for calling screens.

import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.myjetpackcomposeapp.screens.HighSchoolViewModel
import com.example.myjetpackcomposeapp.screens.HighSchoolDetailsScreen
import com.example.myjetpackcomposeapp.screens.HighSchoolListScreen


@Composable
fun MainNavigation() {
val navController = rememberNavController()
val viewModel : HighSchoolViewModel = hiltViewModel()
NavHost(navController, startDestination = Routes.MainRoute.SchoolList.route) {
composable(route = Routes.MainRoute.SchoolList.route){
HighSchoolListScreen(navController,viewModel)
}
composable(route = Routes.MainRoute.SchoolDetails.route) {
HighSchoolDetailsScreen(navController,viewModel)
}
}
}


Step 17 : Create MainActivity for calling MainNavigation screens.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.example.myjetpackcomposeapp.nav.MainNavigation
import com.example.myjetpackcomposeapp.ui.theme.MyJetpackComposeAppTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyJetpackComposeAppTheme {
MainNavigation()
}
}
}
}


Reference Site

here are the reference site of Jetpack compose mvvm retrofit example github you can easily download code and use in your android studio.


Conclusion 

In this post i have shown you how to create Jetpack Compose Mvvm Retrofit Example step by step in 2024. you can easily use this code and modify according to your use.

Here are the final output of this post:

Jetpack Compose Mvvm Retrofit Example
Oversimplified Coding

I am Shubhangam Upadhyay founder of Oversimplified Coding. My motive of this blog to help those developer are just start there carrier in coding line. We cover here Android, Kotlin, Core Java, Jetpack Compose and Flutter etc. related topics. I have 6+ Years of experience in Android Development and working on MNC company as a Senior Android Developer Position. I have worked on 5 different companies and experience on many different type of real time application project.

*

Post a Comment (0)
Previous Post Next Post