← Back to Tutorials Chapter 3

Application Architecture

Android App Architecture: MVVM

Modern Android development follows the MVVM (Model-View-ViewModel) architecture pattern. It separates concerns and makes apps testable and maintainable.

// Model - Data layer
data class User(
    val id: Int,
    val name: String,
    val email: String
)

class UserRepository(private val api: UserApi) {
    suspend fun getUsers(): List<User> = api.fetchUsers()
}

// ViewModel - Presentation logic
class UserViewModel(private val repo: UserRepository) : ViewModel() {
    private val _users = MutableLiveData<List<User>>()
    val users: LiveData<List<User>> = _users

    fun loadUsers() {
        viewModelScope.launch {
            try {
                _users.value = repo.getUsers()
            } catch (e: Exception) {
                _users.value = emptyList()
            }
        }
    }
}

// View - UI layer (Activity/Fragment)
class UserActivity : AppCompatActivity() {
    private lateinit var viewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_users)

        val repo = UserRepository(UserApi())
        viewModel = ViewModelProvider(this, UserViewModelFactory(repo))
            .get(UserViewModel::class.java)

        viewModel.users.observe(this) { users ->
            // Update RecyclerView adapter
        }

        viewModel.loadUsers()
    }
}

Jetpack Components

Android Jetpack is a suite of libraries to help you follow best practices:

Clean Architecture Layers

// Domain Layer - Business logic
interface UserRepository {
    suspend fun getUsers(): Result<List<User>>
}

class GetUsersUseCase(private val repo: UserRepository) {
    suspend operator fun invoke(): Result<List<User>> = repo.getUsers()
}

// Data Layer - Implementations
class UserRepositoryImpl(
    private val api: UserApi,
    private val dao: UserDao
) : UserRepository {
    override suspend fun getUsers(): Result<List<User>> = runCatching {
        val cached = dao.getAllUsers()
        if (cached.isNotEmpty()) cached
        else api.fetchUsers().also { dao.insertAll(it) }
    }
}

// Presentation Layer
@HiltViewModel
class UserViewModel @Inject constructor(
    private val getUsers: GetUsersUseCase
) : ViewModel() {
    val uiState: StateFlow<UiState<List<User>>> = ...
}

Dependency Injection with Hilt

// app/build.gradle.kts
plugins { id("com.google.dagger.hilt.android") }

dependencies {
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-compiler:2.48")
}

// Application class
@HiltAndroidApp
class App : Application()

// Activity
@AndroidEntryPoint
class MainActivity : AppCompatActivity()

Navigation Component

<!-- res/navigation/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/homeFragment">

    <fragment android:id="@+id/homeFragment"
        android:name=".HomeFragment">
        <action android:id="@+id/to_detail"
            app:destination="@id/detailFragment" />
    </fragment>

    <fragment android:id="@+id/detailFragment"
        android:name=".DetailFragment" />
</navigation>

// In code
findNavController().navigate(R.id.to_detail, bundleOf("id" to 123))
Exercise: Create an Android app that follows MVVM architecture. Use a ViewModel with LiveData to hold a list of items, a RecyclerView in the Activity to display them, and a Repository pattern to provide the data (use a mock data source). Add a loading state using LiveData.