Retrofit with MVVM in Jetpack Compose

Search for a command to run...

Why not wrap the getPosts function return type with a Retrofit Response to check for a successful response from ApiService?
Thank you for your suggestion! In this specific case, the response handling is already implemented within the getPost() function of the MainViewModel. The Flow<List<Post>> returned by mainRepository.getPost() allows us to handle success and error scenarios using the collect operator. Therefore, explicitly wrapping the return type with Response<List<Post>> may not be necessary in this context. Nonetheless, I appreciate your input and consideration.
In this series, I will update on android deveopment and kotlin, java code with all latest updates.
The aspect ratio of an image is a proportional relationship between its width and height, We will change aspect ratio of any image in android.
I had problems like when we switch branches, UI files like drawables, icons, xml files etc gets in android studio and caches and even after doing basic invalidate caches restart etc it didnt worked, so after compiling all steps leds to soltion , here...

In today's discussion, we delve into implementing bottom navigation in Jetpack Compose, which can be particularly challenging when combined with nested navigation. While several methods are available, developers often encounter difficulties with navi...

A Clean Architecture Guide for Building Modern Android Apps

Master NavController, NavHost, and More

Introduction LangChain is an open-source framework for developing applications powered by large language models (LLMs). It provides a high-level API that makes it easy to chain together multiple LLMs, as well as other data sources and tools, to creat...

We will build simple app using jsonplaceholderAPI for this project and use Retrofit library with MVVM in JetPack Compose.
So here are the files.
Project Level Build.gradle:
buildscript {
ext {
compose_ui_version = '1.1.1'
hilt_version = '2.39.1'
room_version = "2.4.3"
accompanist_version = '0.25.0'
lifecycle_version = "2.6.0-alpha01"
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id 'com.google.dagger.hilt.android' version '2.41' apply false
}
App Level Build.gradle:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
namespace 'com.developersmarket.componentscompose'
compileSdk 32
defaultConfig {
applicationId "com.developersmarket.componentscompose"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled 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.1.1'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.3.1'
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
implementation 'androidx.compose.material:material:1.1.1'
testImplementation 'junit:junit:4.13.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:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
//Room
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//moshi
implementation("com.squareup.moshi:moshi-kotlin:1.12.0")
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
//dagger hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
kapt("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.4.2")
//kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
// android ktx
implementation 'androidx.activity:activity-ktx:1.5.1'
//hilt viewmodel
// implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
//pager
implementation "com.google.accompanist:accompanist-pager:$accompanist_version"
implementation "com.google.accompanist:accompanist-pager-indicators:$accompanist_version"
//navigation
implementation("androidx.navigation:navigation-compose:2.5.1")
// Local unit tests
testImplementation "androidx.test:core:1.4.0"
testImplementation "junit:junit:4.13.2"
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
testImplementation "com.google.truth:truth:1.1.3"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.1"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.2.1"
// Instrumentation tests
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.41'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.42'
androidTestImplementation "junit:junit:4.13.2"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "com.google.truth:truth:1.1.3"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.9.1"
androidTestImplementation 'androidx.test:runner:1.4.0'
}

Create Necessary Packages As needed.
Lets Start with
BaseApplication.kt:
package com.developersmarket.componentscompose
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class BaseApplication: Application() {}
In AndroidManifest Declare it as name like below :
android:name=".BaseApplication"
Complete AndroidManifest.xml:
<?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: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:name=".BaseApplication"
android:theme="@style/Theme.ComponentsCompose"
tools:targetApi="31">
<activity
android:name=".retrofit.ui.RetrofitActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ComponentsCompose">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
//
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ComponentsCompose">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
Note Above MainActivity is Not used we will be using RetrofitActivity.
network/ApiService.kt:
package com.developersmarket.componentscompose.retrofit.network
import com.developersmarket.componentscompose.retrofit.Post
import retrofit2.http.GET
interface ApiService {
companion object {
const val BASE_URL = "https://jsonplaceholder.typicode.com/"
}
@GET("posts")
suspend fun getPosts(): List<Post>
}
di/AppModule.kt:
package com.developersmarket.componentscompose.retrofit.di
import com.developersmarket.componentscompose.retrofit.network.ApiService
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides @Singleton fun provideMoshi(): Moshi =
Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
@Provides @Singleton fun providesApiService(moshi: Moshi): ApiService =
Retrofit.Builder()
.run {
baseUrl(ApiService.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}.create(ApiService::class.java)
}
MainRepository.kt:
package com.developersmarket.componentscompose.retrofit
import com.developersmarket.componentscompose.retrofit.network.ApiService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
class MainRepository
@Inject
constructor(private val apiService: ApiService){
fun getPost(): Flow<List<Post>> = flow {
emit(apiService.getPosts())
}.flowOn(Dispatchers.IO)
}
util/DataSource.kt:
package com.developersmarket.componentscompose.util
data class User(
val description: String
)
fun dummyData():List<User>{
return listOf(
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
User("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."),
)
}
util.ApiState:
package com.developersmarket.componentscompose.util
import com.developersmarket.componentscompose.retrofit.Post
sealed class ApiState {
class Success(val data: List<Post>) : ApiState()
class Failure(val msg: Throwable) : ApiState()
object Loading:ApiState()
object Empty: ApiState()
}
ui/MainViewModel.kt
package com.developersmarket.componentscompose.retrofit.ui
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.developersmarket.componentscompose.retrofit.MainRepository
import com.developersmarket.componentscompose.util.ApiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class MainViewModel
@Inject constructor(private val mainRepository: MainRepository) : ViewModel() {
val response: MutableState<ApiState> = mutableStateOf(ApiState.Empty)
init {
getPost()
}
fun getPost() =
viewModelScope.launch {
mainRepository.getPost().onStart {
response.value= ApiState.Loading
}.catch {
response.value= ApiState.Failure(it)
}.collect {
response.value=ApiState.Success(it)
}
}
}
RetrofitActivity.kt
package com.developersmarket.componentscompose.retrofit.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.developersmarket.componentscompose.retrofit.Post
import com.developersmarket.componentscompose.ui.theme.ComponentsComposeTheme
import com.developersmarket.componentscompose.util.ApiState
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class RetrofitActivity : ComponentActivity() {
private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComponentsComposeTheme() {
Surface(color = MaterialTheme.colors.background) {
GETData(mainViewModel)
}
}
}
}
@Composable fun EachRow(post: Post) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 8.dp,
vertical = 8.dp
),
elevation = 2.dp,
shape = RoundedCornerShape(4.dp)
) {
Text(
text = post.body,
modifier = Modifier.padding(10.dp)
)
}
}
@Composable fun GETData(mainViewModel: MainViewModel) {
when (val result = mainViewModel.response.value) {
is ApiState.Success -> {
LazyColumn {
items(result.data) { response ->
EachRow(post = response)
}
}
}
is ApiState.Failure -> {
Text(text = "${result.msg}")
}
ApiState.Loading -> {
CircularProgressIndicator()
}
ApiState.Empty -> {
}
}
}
}
Thats It :)

https://github.com/saurabhthesuperhero/ComposeRetrofit
if you find Something isnt working do check imports properly sometime wrong imports cause all the problems, yet all the best, and thanks for reading.