Bird
Raised Fist0
Android Kotlinmobile~20 mins

Room with Coroutines in Android Kotlin - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Notes List
A simple screen to display a list of notes stored in a Room database using Kotlin Coroutines for asynchronous operations.
Target UI
-------------------------
|       Notes List       |
-------------------------
| - Note 1               |
| - Note 2               |
| - Note 3               |
|                       |
| [+] Add Note           |
-------------------------
Use Room database to store notes with id and text fields.
Use Kotlin Coroutines to fetch notes asynchronously.
Display notes in a vertical list.
Add a FloatingActionButton with '+' icon to add a new note.
When '+' is tapped, add a new note with sample text to the database and refresh the list.
Starter Code
Android Kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NotesScreen()
        }
    }
}

@Composable
fun NotesScreen() {
    // TODO: Implement UI and data loading
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
Android Kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import androidx.room.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val text: String
)

@Dao
interface NoteDao {
    @Query("SELECT * FROM notes")
    suspend fun getAll(): List<Note>

    @Insert
    suspend fun insert(note: Note)
}

@Database(entities = [Note::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun noteDao(): NoteDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: android.content.Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "notes_db"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

class MainActivity : ComponentActivity() {
    private lateinit var db: AppDatabase

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        db = AppDatabase.getInstance(this)

        setContent {
            NotesScreen(db)
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NotesScreen(db: AppDatabase) {
    var notes by remember { mutableStateOf<List<Note>>(emptyList()) }
    val scope = rememberCoroutineScope()

    LaunchedEffect(Unit) {
        notes = withContext(Dispatchers.IO) {
            db.noteDao().getAll()
        }
    }

    Scaffold(
        floatingActionButton = {
            FloatingActionButton(
                onClick = {
                    scope.launch(Dispatchers.IO) {
                        db.noteDao().insert(Note(text = "New note"))
                        val updatedNotes = db.noteDao().getAll()
                        withContext(Dispatchers.Main) {
                            notes = updatedNotes
                        }
                    }
                },
                containerColor = MaterialTheme.colorScheme.primary
            ) {
                Icon(Icons.Default.Add, contentDescription = "Add Note")
            }
        },
        content = { paddingValues ->
            LazyColumn(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(paddingValues)
                    .padding(16.dp)
            ) {
                items(notes) { note ->
                    Text(text = note.text, modifier = Modifier.padding(vertical = 8.dp))
                    Divider()
                }
            }
        }
    )
}

This solution uses Room to store notes with an id and text. The NoteDao interface defines suspend functions to get all notes and insert a new note, which allows using Kotlin Coroutines for asynchronous database operations.

The AppDatabase singleton provides the Room database instance.

In MainActivity, the database instance is created and passed to the NotesScreen composable.

The NotesScreen composable uses remember and mutableStateOf to hold the list of notes. It loads notes asynchronously on launch with LaunchedEffect and updates the UI.

The FloatingActionButton adds a new note asynchronously on click, then reloads the notes and updates the UI on the main thread.

This approach keeps the UI responsive and follows Android best practices for Room and Coroutines.

Final Result
Completed Screen
-------------------------
|       Notes List       |
-------------------------
| New note               |
| New note               |
| New note               |
|                       |
| [+] Add Note           |
-------------------------
User sees a list of notes with their text.
User taps the '+' button to add a new note.
A new note with text 'New note' appears at the bottom of the list.
Stretch Goal
Add a text input dialog to enter custom note text before adding it to the database.
💡 Hint
Use Compose AlertDialog with TextField and buttons to get user input, then insert the note with that text.

Practice

(1/5)
1. What is the main benefit of using Coroutines with Room in Android development?
easy
A. They replace the need for SQL queries.
B. They automatically create database tables.
C. They allow database operations to run without freezing the app UI.
D. They make the app use less memory.

Solution

  1. Step 1: Understand Coroutines role

    Coroutines let tasks run in the background without blocking the main app thread.
  2. Step 2: Connect Coroutines with Room

    Room uses suspend functions with Coroutines to perform database operations smoothly without freezing UI.
  3. Final Answer:

    They allow database operations to run without freezing the app UI. -> Option C
  4. Quick Check:

    Coroutines keep UI responsive = A [OK]
Hint: Coroutines keep UI smooth by running tasks in background [OK]
Common Mistakes:
  • Thinking Coroutines create tables automatically
  • Believing Coroutines replace SQL queries
  • Assuming Coroutines reduce app memory usage directly
2. Which of the following is the correct way to declare a suspend function in a Room DAO interface?
easy
A. suspend fun getAllUsers(): List<User>
B. fun getAllUsers(): List<User>
C. async fun getAllUsers(): List<User>
D. fun suspend getAllUsers(): List<User>

Solution

  1. Step 1: Recall suspend function syntax

    A suspend function is declared with the keyword suspend before fun.
  2. Step 2: Check each option

    suspend fun getAllUsers(): List<User> correctly uses suspend fun. Plain fun getAllUsers(): List<User> lacks the suspend keyword. fun suspend getAllUsers(): List<User> has the keywords in wrong order. async fun getAllUsers(): List<User> uses async which is not valid here.
  3. Final Answer:

    suspend fun getAllUsers(): List<User> -> Option A
  4. Quick Check:

    suspend keyword before fun = B [OK]
Hint: Remember: suspend keyword always comes before fun [OK]
Common Mistakes:
  • Placing suspend after fun
  • Using async instead of suspend
  • Omitting suspend keyword for coroutine functions
3. Given this DAO function:
 @Query("SELECT * FROM users")
 suspend fun getUsers(): List<User>

What happens when you call getUsers() from a coroutine?
medium
A. The function blocks the main thread until data is fetched.
B. The function suspends the coroutine until data is fetched, then returns the list.
C. The function returns immediately with an empty list.
D. The function throws a compile-time error.

Solution

  1. Step 1: Understand suspend function behavior

    A suspend function pauses the coroutine without blocking the thread until the result is ready.
  2. Step 2: Apply to Room DAO call

    Calling getUsers() suspends the coroutine until Room fetches data, then returns the list.
  3. Final Answer:

    The function suspends the coroutine until data is fetched, then returns the list. -> Option B
  4. Quick Check:

    suspend functions pause coroutine, not block thread = C [OK]
Hint: Suspend functions pause coroutine, not block main thread [OK]
Common Mistakes:
  • Thinking suspend blocks main thread
  • Expecting immediate empty list return
  • Assuming compile-time error on suspend
4. You wrote this DAO method:
@Query("SELECT * FROM users")
fun getUsers(): List<User>

When calling getUsers() from a coroutine, what problem will occur?
medium
A. The app will crash with a runtime error about main thread database access.
B. The function will suspend correctly and return data.
C. The function will return null instead of data.
D. The function will compile but never return.

Solution

  1. Step 1: Identify missing suspend keyword

    The DAO method is not marked suspend, so it runs on the calling thread.
  2. Step 2: Understand Room's main thread policy

    Room forbids database queries on the main thread to avoid freezing UI, causing runtime crash if violated.
  3. Final Answer:

    The app will crash with a runtime error about main thread database access. -> Option A
  4. Quick Check:

    Non-suspend DAO on main thread causes crash = A [OK]
Hint: Always mark DAO queries suspend to avoid main thread crash [OK]
Common Mistakes:
  • Forgetting suspend keyword in DAO
  • Expecting null return on wrong thread
  • Assuming silent failure without crash
5. You want to insert a list of users into the database using Room and Coroutines. Which DAO method signature correctly supports this operation without blocking the UI?
@Insert
??? fun insertUsers(users: List<User>)
hard
A. fun insertUsers(users: List<User>)
B. async fun insertUsers(users: List<User>)
C. fun insertUsers(users: List<User>): Deferred<Unit>
D. suspend fun insertUsers(users: List<User>)

Solution

  1. Step 1: Identify correct coroutine support

    To run database operations without blocking UI, DAO methods must be suspend functions.
  2. Step 2: Evaluate options

    suspend fun insertUsers(users: List<User>) uses suspend fun, which is correct. fun insertUsers(users: List<User>) blocks UI. fun insertUsers(users: List<User>): Deferred<Unit> returns Deferred which is not standard DAO signature. async fun insertUsers(users: List<User>) uses invalid async keyword.
  3. Final Answer:

    suspend fun insertUsers(users: List<User>) -> Option D
  4. Quick Check:

    Use suspend fun for non-blocking Room inserts = D [OK]
Hint: Use suspend fun for DAO inserts to keep UI smooth [OK]
Common Mistakes:
  • Omitting suspend causing UI freeze
  • Using async keyword incorrectly
  • Returning Deferred instead of suspend function
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