------------------------- | Notes List | ------------------------- | - Note 1 | | - Note 2 | | - Note 3 | | | | [+] Add Note | -------------------------
Room with Coroutines 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.* 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 }
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.
------------------------- | Notes List | ------------------------- | New note | | New note | | New note | | | | [+] Add Note | -------------------------
Practice
Coroutines with Room in Android development?Solution
Step 1: Understand Coroutines role
Coroutines let tasks run in the background without blocking the main app thread.Step 2: Connect Coroutines with Room
Room uses suspend functions with Coroutines to perform database operations smoothly without freezing UI.Final Answer:
They allow database operations to run without freezing the app UI. -> Option CQuick Check:
Coroutines keep UI responsive = A [OK]
- Thinking Coroutines create tables automatically
- Believing Coroutines replace SQL queries
- Assuming Coroutines reduce app memory usage directly
Solution
Step 1: Recall suspend function syntax
A suspend function is declared with the keywordsuspendbeforefun.Step 2: Check each option
suspend fun getAllUsers(): List<User>correctly usessuspend fun. Plainfun getAllUsers(): List<User>lacks thesuspendkeyword.fun suspend getAllUsers(): List<User>has the keywords in wrong order.async fun getAllUsers(): List<User>usesasyncwhich is not valid here.Final Answer:
suspend fun getAllUsers(): List<User> -> Option AQuick Check:
suspend keyword before fun = B [OK]
- Placing suspend after fun
- Using async instead of suspend
- Omitting suspend keyword for coroutine functions
@Query("SELECT * FROM users")
suspend fun getUsers(): List<User>What happens when you call
getUsers() from a coroutine?Solution
Step 1: Understand suspend function behavior
A suspend function pauses the coroutine without blocking the thread until the result is ready.Step 2: Apply to Room DAO call
CallinggetUsers()suspends the coroutine until Room fetches data, then returns the list.Final Answer:
The function suspends the coroutine until data is fetched, then returns the list. -> Option BQuick Check:
suspend functions pause coroutine, not block thread = C [OK]
- Thinking suspend blocks main thread
- Expecting immediate empty list return
- Assuming compile-time error on suspend
@Query("SELECT * FROM users")
fun getUsers(): List<User>When calling
getUsers() from a coroutine, what problem will occur?Solution
Step 1: Identify missing suspend keyword
The DAO method is not marked suspend, so it runs on the calling thread.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.Final Answer:
The app will crash with a runtime error about main thread database access. -> Option AQuick Check:
Non-suspend DAO on main thread causes crash = A [OK]
- Forgetting suspend keyword in DAO
- Expecting null return on wrong thread
- Assuming silent failure without crash
@Insert ??? fun insertUsers(users: List<User>)
Solution
Step 1: Identify correct coroutine support
To run database operations without blocking UI, DAO methods must be suspend functions.Step 2: Evaluate options
suspend fun insertUsers(users: List<User>) usessuspend 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.Final Answer:
suspend fun insertUsers(users: List<User>) -> Option DQuick Check:
Use suspend fun for non-blocking Room inserts = D [OK]
- Omitting suspend causing UI freeze
- Using async keyword incorrectly
- Returning Deferred instead of suspend function
Ready to prove it?
Pass the test and this pattern moves unknown
