--------------------- | User List | --------------------- | - Alice | | - Bob | | - Charlie | --------------------- | [Add User Button] | ---------------------
Room with Flow for reactive data in Android Kotlin - Mini App: Build & Ship
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.room.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch @Entity data class User( @PrimaryKey(autoGenerate = true) val id: Int = 0, val name: String ) @Dao interface UserDao { // TODO: Define function to get all users as Flow<List<User>> // TODO: Define function to insert a user } @Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "app-db" ).build() val userDao = db.userDao() setContent { MaterialTheme { Surface(modifier = Modifier.fillMaxSize()) { // TODO: Implement UserListScreen composable } } } } }
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.room.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import kotlin.random.Random @Entity data class User( @PrimaryKey(autoGenerate = true) val id: Int = 0, val name: String ) @Dao interface UserDao { @Query("SELECT * FROM User") fun getAllUsers(): Flow<List<User>> @Insert suspend fun insertUser(user: User) } @Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "app-db" ).build() val userDao = db.userDao() setContent { MaterialTheme { Surface(modifier = Modifier.fillMaxSize()) { UserListScreen(userDao) } } } } } @androidx.compose.runtime.Composable fun UserListScreen(userDao: UserDao) { val usersFlow = userDao.getAllUsers() val users by usersFlow.collectAsState(initial = emptyList()) val scope = rememberCoroutineScope() Column { LazyColumn(modifier = Modifier.weight(1f)) { items(users) { user -> Text(text = "- ${user.name}") } } Button(onClick = { scope.launch { val randomName = "User${Random.nextInt(1000)}" userDao.insertUser(User(name = randomName)) } }) { Text("Add User") } } }
This solution uses Room to store User entities with an auto-generated id and a name.
The UserDao interface defines getAllUsers() which returns a Flow<List<User>>. This allows the UI to observe the database reactively.
The insertUser suspend function inserts a new user.
In MainActivity, we build the Room database and get the DAO instance.
The UserListScreen composable collects the Flow as state, so the UI updates automatically when the data changes.
Users are displayed in a scrollable list using LazyColumn. A button below adds a new user with a random name when clicked, launching a coroutine to call the suspend insert function.
This setup demonstrates reactive data flow from Room to UI using Kotlin Flow and Compose.
--------------------- | User List | --------------------- | - User123 | | - User456 | | - User789 | --------------------- | [Add User Button] | ---------------------
Practice
Flow with Room in Android apps?Solution
Step 1: Understand Flow's role in Room
Flow provides a stream of data that emits updates when the database changes.Step 2: Connect Flow to UI updates
Collecting Flow in a coroutine lets the UI reactively update without manual refresh.Final Answer:
It allows the app to automatically update UI when database data changes. -> Option BQuick Check:
Flow = reactive UI updates [OK]
- Thinking Flow speeds up queries
- Confusing Flow with encryption
- Assuming Flow reduces app size
Solution
Step 1: Identify return type for reactive data
Flow is the correct type for a reactive stream of user lists.Step 2: Compare other options
List<User> is static, LiveData<User> is not Flow, User is a single object.Final Answer:
fun getUsers(): Flow<List<User>> -> Option CQuick Check:
Flow return type = fun getUsers(): Flow [OK]
- Returning List instead of Flow
- Confusing LiveData with Flow
- Returning single object instead of list
fun getAllTasks(): Flow<List<Task>>
What happens when you collect this Flow in your UI and a new task is inserted into the database?
Solution
Step 1: Understand Flow behavior on data change
Flow emits new data whenever the underlying database query result changes.Step 2: Apply to task insertion
Inserting a new task triggers Flow to emit the updated full list including the new task.Final Answer:
The Flow emits the updated list including the new task automatically. -> Option AQuick Check:
Flow emits updated list on insert [OK]
- Thinking Flow stops on data change
- Expecting only new item emission
- Assuming Flow throws error on insert
@Query("SELECT * FROM users")
fun getUsers(): List<User>You want reactive updates but your UI does not update after data changes. What is the problem?
Solution
Step 1: Identify return type issue
Returning List<User> gives a static snapshot, no reactive updates.Step 2: Fix return type for reactive data
Changing return type to Flow enables reactive updates on data change.Final Answer:
The function should return Flow<List<User>> instead of List<User>. -> Option AQuick Check:
Return Flow for reactive updates [OK]
- Forgetting to use Flow return type
- Missing @Query annotation (not the case here)
- Not using coroutine to collect Flow
1. DAO function: ? 2. Collect Flow in UI: ?
Solution
Step 1: Choose correct DAO return type
Flow<List<Note>> provides reactive stream of note lists.Step 2: Collect Flow properly in UI
Use lifecycleScope.launch to collect Flow and update UI reactively.Final Answer:
1. fun getNotes(): Flow<List<Note>> 2. lifecycleScope.launch { dao.getNotes().collect { updateUI(it) } } -> Option DQuick Check:
Flow + coroutine collection = reactive UI [OK]
- Returning List instead of Flow
- Not collecting Flow in coroutine
- Confusing LiveData with Flow
Ready to prove it?
Pass the test and this pattern moves unknown
