Bird
Raised Fist0
Android Kotlinmobile~20 mins

Room with Flow for reactive data in Android Kotlin - Mini App: Build & Ship

Choose your learning style10 modes available
Build: User List
Displays a list of users stored in a local Room database. The list updates automatically when data changes using Kotlin Flow.
Target UI
---------------------
| User List         |
---------------------
| - Alice           |
| - Bob             |
| - Charlie         |
---------------------
| [Add User Button] |
---------------------
Use Room database to store User entities with id and name
Create a DAO that returns Flow<List<User>> for all users
Observe the Flow in the UI to update the list reactively
Add a button to insert a new user with a random name
Display the list of users in a vertical scrollable list
Starter Code
Android Kotlin
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
                }
            }
        }
    }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Android Kotlin
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.

Final Result
Completed Screen
---------------------
| User List         |
---------------------
| - User123         |
| - User456         |
| - User789         |
---------------------
| [Add User Button] |
---------------------
When the app starts, the list shows all users from the database.
Tapping 'Add User' inserts a new user with a random name.
The list updates automatically to show the new user without refreshing.
Stretch Goal
Add a swipe-to-delete feature to remove users from the list.
💡 Hint
Use Compose's SwipeToDismiss with a coroutine to call a suspend delete function in UserDao.

Practice

(1/5)
1. What is the main benefit of using Flow with Room in Android apps?
easy
A. It makes the database queries run faster than normal queries.
B. It allows the app to automatically update UI when database data changes.
C. It encrypts the database data for security.
D. It reduces the app size by removing unused code.

Solution

  1. Step 1: Understand Flow's role in Room

    Flow provides a stream of data that emits updates when the database changes.
  2. Step 2: Connect Flow to UI updates

    Collecting Flow in a coroutine lets the UI reactively update without manual refresh.
  3. Final Answer:

    It allows the app to automatically update UI when database data changes. -> Option B
  4. Quick Check:

    Flow = reactive UI updates [OK]
Hint: Flow streams data changes to UI automatically [OK]
Common Mistakes:
  • Thinking Flow speeds up queries
  • Confusing Flow with encryption
  • Assuming Flow reduces app size
2. Which of the following is the correct way to declare a DAO function that returns a Flow list of users?
easy
A. fun getUsers(): List<User>
B. fun getUsers(): LiveData<User>
C. fun getUsers(): Flow
D. fun getUsers(): User

Solution

  1. Step 1: Identify return type for reactive data

    Flow is the correct type for a reactive stream of user lists.
  2. Step 2: Compare other options

    List<User> is static, LiveData<User> is not Flow, User is a single object.
  3. Final Answer:

    fun getUsers(): Flow<List<User>> -> Option C
  4. Quick Check:

    Flow return type = fun getUsers(): Flow [OK]
Hint: Use Flow for reactive list queries [OK]
Common Mistakes:
  • Returning List instead of Flow
  • Confusing LiveData with Flow
  • Returning single object instead of list
3. Given this DAO function:
fun getAllTasks(): Flow<List<Task>>

What happens when you collect this Flow in your UI and a new task is inserted into the database?
medium
A. The Flow emits the updated list including the new task automatically.
B. The Flow stops emitting data until restarted.
C. The Flow emits only the new task, not the full list.
D. The Flow throws an exception because of data change.

Solution

  1. Step 1: Understand Flow behavior on data change

    Flow emits new data whenever the underlying database query result changes.
  2. Step 2: Apply to task insertion

    Inserting a new task triggers Flow to emit the updated full list including the new task.
  3. Final Answer:

    The Flow emits the updated list including the new task automatically. -> Option A
  4. Quick Check:

    Flow emits updated list on insert [OK]
Hint: Flow emits full updated data on DB changes [OK]
Common Mistakes:
  • Thinking Flow stops on data change
  • Expecting only new item emission
  • Assuming Flow throws error on insert
4. You wrote this DAO function:
@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?
medium
A. The function should return Flow instead of List<User>.
B. The @Query annotation is missing.
C. You need to call the function inside a coroutine.
D. The database must be closed before updates.

Solution

  1. Step 1: Identify return type issue

    Returning List<User> gives a static snapshot, no reactive updates.
  2. Step 2: Fix return type for reactive data

    Changing return type to Flow enables reactive updates on data change.
  3. Final Answer:

    The function should return Flow<List<User>> instead of List<User>. -> Option A
  4. Quick Check:

    Return Flow for reactive updates [OK]
Hint: Return Flow to get reactive data updates [OK]
Common Mistakes:
  • Forgetting to use Flow return type
  • Missing @Query annotation (not the case here)
  • Not using coroutine to collect Flow
5. You want to display a list of notes that updates automatically when the database changes. Which combination correctly implements this using Room and Flow?
1. DAO function: ?
2. Collect Flow in UI: ?
hard
A. 1. fun getNotes(): List<Note> 2. Call getNotes() once in onCreate()
B. 1. fun getNotes(): Flow<Note> 2. Collect Flow without coroutine
C. 1. fun getNotes(): LiveData 2. Observe LiveData with observe()
D. 1. fun getNotes(): Flow 2. lifecycleScope.launch { dao.getNotes().collect { updateUI(it) } }

Solution

  1. Step 1: Choose correct DAO return type

    Flow<List<Note>> provides reactive stream of note lists.
  2. Step 2: Collect Flow properly in UI

    Use lifecycleScope.launch to collect Flow and update UI reactively.
  3. Final Answer:

    1. fun getNotes(): Flow<List<Note>> 2. lifecycleScope.launch { dao.getNotes().collect { updateUI(it) } } -> Option D
  4. Quick Check:

    Flow + coroutine collection = reactive UI [OK]
Hint: Use Flow in DAO and collect in coroutine for reactive UI [OK]
Common Mistakes:
  • Returning List instead of Flow
  • Not collecting Flow in coroutine
  • Confusing LiveData with Flow
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard