• Latest Code...

    Featured Post

    Implementing Hilt in a Kotlin Android Jetpack Compose Project with MVVM Architecture

     In modern Android development, maintaining a scalable codebase can be challenging, especially when it comes to dependency management. Hilt,...

    Implementing REST API Integration in Android Apps Using Jetpack Compose and Modern Architecture


    Designing a robust, maintainable, and scalable Android application requires implementing solid architecture principles and leveraging modern tools and components. This article provides a comprehensive guide to building an app with MVVM (Model-View-ViewModel) and Clean Architecture using the latest Android components: Coroutines, Hilt, Jetpack Compose, Retrofit, and Gson. We'll use the Star Wars API (https://swapi.dev/api/people/) as an example.




    Why MVVM and Clean Architecture?

    • MVVM: Separates UI (View) from business logic (ViewModel) and data (Model), making the codebase more manageable and testable.
    • Clean Architecture: Divides the app into layers (Presentation, Domain, and Data) to enforce clear separation of concerns, making the code more reusable and easier to modify.
    • Retrofit: A type-safe HTTP client for Android and Java, making it easy to fetch data from a REST API.
    • Gson: A library for converting Java objects into JSON and vice versa, which is ideal for handling API responses.
    • Jetpack Compose: The modern UI toolkit for building native Android apps with declarative syntax, providing a more intuitive way to design interfaces.
    • Hilt: It simplifies the DI process by generating the necessary components at compile-time, allowing us to inject dependencies such as the Retrofit service and the CharacterRepository without manually writing boilerplate code.

    App Structure and Folder Format

    Here's a sample folder structure for our app:

    com.example.starwarsapp
    ├── data
    │   ├── api
    │   │   └── StarWarsApiService.kt
    │   ├── model
    │   │   └── Character.kt
    │   ├── repository
    │       └── CharacterRepository.kt
    ├── di
    │   └── AppModule.kt
    ├── domain
    │   ├── model
    │   │   └── CharacterDomainModel.kt
    │   ├── repository
    │   │   └── CharacterRepositoryInterface.kt
    │   └── usecase
    │       └── GetCharactersUseCase.kt
    ├── presentation
    │   ├── ui
    │   │   ├── theme
    │   │   │   └── Theme.kt
    │   │   └── CharacterListScreen.kt
    │   └── viewmodel
    │       └── CharacterViewModel.kt
    └── MainActivity.kt

    Step-by-Step Implementation

    1. Dependencies in build.gradle

    dependencies {
        // Retrofit for API requests
        implementation 'com.squareup.retrofit2:retrofit:2.9.0'
        implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
        
        // Hilt for dependency injection
        implementation 'com.google.dagger:hilt-android:2.48'
        kapt 'com.google.dagger:hilt-compiler:2.48'
        
        // Jetpack Compose
        implementation 'androidx.compose.ui:ui:1.5.0'
        implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
        
        // Kotlin Coroutines
        implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
    }

    2. API Service

    StarWarsApiService.kt

    package com.example.starwarsapp.data.api
    
    import retrofit2.http.GET
    import com.example.starwarsapp.data.model.Character
    
    interface StarWarsApiService {
        @GET("people/")
        suspend fun getCharacters(): List<Character>
    }

    3. Model Classes

    API Data Model

    Character.kt

    package com.example.starwarsapp.data.model
    
    data class Character(
        val name: String,
        val gender: String
    )

    Domain Model

    CharacterDomainModel.kt

    package com.example.starwarsapp.domain.model
    
    data class CharacterDomainModel(
        val name: String,
        val gender: String
    )

    4. Repository

    CharacterRepository.kt

    package com.example.starwarsapp.data.repository
    
    import com.example.starwarsapp.data.api.StarWarsApiService
    import com.example.starwarsapp.domain.model.CharacterDomainModel
    
    class CharacterRepository(private val apiService: StarWarsApiService) {
        suspend fun fetchCharacters(): List<CharacterDomainModel> {
            return apiService.getCharacters().map {
                CharacterDomainModel(name = it.name, gender = it.gender)
            }
        }
    }

    5. Use Case

    GetCharactersUseCase.kt

    package com.example.starwarsapp.domain.usecase
    
    import com.example.starwarsapp.data.repository.CharacterRepository
    import com.example.starwarsapp.domain.model.CharacterDomainModel
    
    class GetCharactersUseCase(private val repository: CharacterRepository) {
        suspend operator fun invoke(): List<CharacterDomainModel> {
            return repository.fetchCharacters()
        }
    }

    6. ViewModel

    CharacterViewModel.kt

    package com.example.starwarsapp.presentation.viewmodel
    
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import com.example.starwarsapp.domain.model.CharacterDomainModel
    import com.example.starwarsapp.domain.usecase.GetCharactersUseCase
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.StateFlow
    import kotlinx.coroutines.launch
    
    class CharacterViewModel(private val getCharactersUseCase: GetCharactersUseCase) : ViewModel() {
        private val _characters = MutableStateFlow<List<CharacterDomainModel>>(emptyList())
        val characters: StateFlow<List<CharacterDomainModel>> get() = _characters
    
        init {
            fetchCharacters()
        }
    
        private fun fetchCharacters() {
            viewModelScope.launch {
                _characters.value = getCharactersUseCase()
            }
        }
    }

    7. Dependency Injection

    AppModule.kt

    package com.example.starwarsapp.di
    
    import com.example.starwarsapp.data.api.StarWarsApiService
    import com.example.starwarsapp.data.repository.CharacterRepository
    import com.example.starwarsapp.domain.usecase.GetCharactersUseCase
    import dagger.Module
    import dagger.Provides
    import dagger.hilt.InstallIn
    import dagger.hilt.components.SingletonComponent
    import retrofit2.Retrofit
    import retrofit2.converter.gson.GsonConverterFactory
    
    @Module
    @InstallIn(SingletonComponent::class)
    object AppModule {
        @Provides
        fun provideRetrofit(): Retrofit = Retrofit.Builder()
            .baseUrl("https://swapi.dev/api/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    
        @Provides
        fun provideStarWarsApi(retrofit: Retrofit): StarWarsApiService =
            retrofit.create(StarWarsApiService::class.java)
    
        @Provides
        fun provideCharacterRepository(apiService: StarWarsApiService) =
            CharacterRepository(apiService)
    
        @Provides
        fun provideGetCharactersUseCase(repository: CharacterRepository) =
            GetCharactersUseCase(repository)
    }

    8. Compose UI

    CharacterListScreen.kt

    package com.example.starwarsapp.presentation.ui
    
    import androidx.compose.foundation.layout.*
    import androidx.compose.foundation.lazy.LazyColumn
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.collectAsState
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.unit.dp
    import com.example.starwarsapp.presentation.viewmodel.CharacterViewModel
    
    @Composable
    fun CharacterListScreen(viewModel: CharacterViewModel) {
        val characters = viewModel.characters.collectAsState().value
    
        LazyColumn(modifier = Modifier.fillMaxSize().padding(16.dp)) {
            items(characters.size) { index ->
                val character = characters[index]
                Column(modifier = Modifier.padding(8.dp)) {
                    Text(text = "Name: ${character.name}")
                    Text(text = "Gender: ${character.gender}")
                }
            }
        }
    }

    9. Main Activity

    MainActivity.kt

    package com.example.starwarsapp
    
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import com.example.starwarsapp.presentation.ui.CharacterListScreen
    import com.example.starwarsapp.presentation.viewmodel.CharacterViewModel
    import dagger.hilt.android.AndroidEntryPoint
    import javax.inject.Inject
    
    @AndroidEntryPoint
    class MainActivity : ComponentActivity() {
        @Inject lateinit var viewModel: CharacterViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                CharacterListScreen(viewModel = viewModel)
            }
        }
    }

    Conclusion

    This app architecture demonstrates the seamless integration of MVVM and Clean Architecture principles using modern tools like Compose, Hilt, and Coroutines. By following this pattern, you ensure scalability, testability, and maintainability for your app. 

    Happy coding!


    What’s your favorite Kotlin string manipulation tip? Share in the comments below!

    Contact Form

    Name

    Email *

    Message *