π️ Building Robust Android Apps with MVVM Architecture in Jetpack Compose
π Why MVVM Matters in Jetpack Compose
- Cleaner architecture
- Better testability
- Responsiveness to state changes
How to Build a Food App using Jetpack Compose and MVVM architecture?
π Understanding MVVM in Compose Context
| Layer | Responsibility |
|---|---|
| Model | Manages and represents the data (e.g., API, DB, or cache) |
| ViewModel | Contains business logic and exposes UI data via state |
| View | Composable functions that display UI and react to ViewModel state |
1. What is the role of a ViewModel in Jetpack Compose, and how does it interact with Composables?
2. How do you expose UI state from a ViewModel to a Composable in a lifecycle-aware way?
π State Management
4. How do you handle one-time events like navigation or showing a toast/snackbar in MVVM with Compose?
π Best Practices
5. How do you separate concerns between UI logic and business logic in MVVM with Compose?
6. What are some anti-patterns to avoid when using ViewModel with Compose?
7. In a multi-screen Compose app, how do you share a ViewModel between screens? When should you or shouldn't you?
π Dependency Injection
8. How do you inject dependencies (like repositories or use cases) into a ViewModel?
9. How does Hilt work with ViewModels in a Jetpack Compose environment?
π Real-World Scenarios
10. Your app has complex user input forms. How would you structure ViewModel and Composables to manage validation, user input, and UI feedback?
11. In a paginated list (e.g., news feed), how would your ViewModel manage API calls, loading state, and error handling?
12. How do you persist ViewModel state across process death or configuration changes in a Compose app?
π Testing MVVM in Compose
13. How would you test a ViewModel that exposes StateFlow to a Composable?
14. What strategies do you use to test ViewModel logic separately from the UI in Compose apps?
π§± MVVM Architecture: Full Example
π¦ 1. Data Layer (Model + Repository)
Book.kt
package com.example.mvvmapp
data class Book(
val id: Int,
val title: String,
val author: String
)
BookRepository.kt
package com.example.mvvmapp
class BookRepository {
fun fetchBooks(): List<Book> {
return listOf(
Book(1, "Atomic Habits", "James Clear"),
Book(2, "Clean Code", "Robert C. Martin"),
Book(3, "The Alchemist", "Paulo Coelho")
)
}
}
π 2. ViewModel Layer
BookViewModel.kt
package com.example.mvvmapp
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class BookViewModel : ViewModel() {
private val repository = BookRepository()
// Expose UI state as immutable
private val _books = mutableStateOf<List<Book>>(emptyList())
val books: State<List<Book>> = _books
init {
loadBooks()
}
private fun loadBooks() {
_books.value = repository.fetchBooks()
}
}
π¨ 3. View Layer (Composable UI)
BookListScreen.kt
package com.example.mvvmapp
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BookListScreen(viewModel: BookViewModel = viewModel()) {
val bookList by viewModel.books
Scaffold(
topBar = {
TopAppBar(title = { Text("Library") })
}
) { paddingValues ->
LazyColumn(contentPadding = paddingValues) {
items(bookList) { book ->
Column(modifier = Modifier.padding(16.dp)) {
Text(text = book.title, style = MaterialTheme.typography.titleMedium)
Text(text = "by ${book.author}", style = MaterialTheme.typography.bodySmall)
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
}
}
}
}
}
π 4. App Entry Point
MainActivity.kt
package com.example.mvvmapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.example.mvvmapp.ui.theme.MVVMAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MVVMAppTheme {
/* Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}*/
BookListScreen()
}
}
}
}



