# Retrofit with MVVM in Jetpack Compose

We will build simple app using jsonplaceholderAPI for this project and use Retrofit library with MVVM in JetPack Compose.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">If you are looking for Jetpack Compose ROOMDB Setup with Flow, DI and MVVM : <a target="_blank" rel="noopener noreferrer nofollow" href="https://saurabhjadhavblogs.com/compose-mvvm-roomdb-with-flow-and-di" style="pointer-events: none">Article : Compose MVVM RoomDB with Flow and DI</a></div>
</div>

So here are the files.

### Dependencies:

Project Level Build.gradle:

```java
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:

```java
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'

}
```

### File Structure:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675421788316/2a0cddbb-28e8-421f-9eec-a3651b490e30.png align="center")

Create Necessary Packages As needed.

Lets Start with

**BaseApplication.kt**:

```java
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 :

```java
android:name=".BaseApplication"
```

Complete **AndroidManifest.xml:**

```java
<?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.

### Retrofit Setup:

**network/ApiService.kt:**

```java
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:**

```java
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:**

```java
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)
}
```

### Utils

**util/DataSource.kt:**

```java
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:**

```java
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()
}
```

### ViewModel:

**ui/MainViewModel.kt**

```java
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)
            }
        }
}
```

### Final Activity:

**RetrofitActivity.kt**

```java
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 :)

### Output:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675422331600/1526cddd-eb26-4e53-9eab-765dbe65bc4f.png align="center")

### Source Code:

[https://github.com/saurabhthesuperhero/ComposeRetrofit](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.
