Kotlin Room Database Basics
Room is Android's official database library. It provides an abstraction layer over SQLite, replacing raw SQL queries with Kotlin-annotated classes. Room verifies your queries at compile time, integrates seamlessly with coroutines, and works naturally with LiveData and Flow.
Room Architecture
Your Code
│
▼
┌───────────────────────────────────────────────────┐
│ Room │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ @Entity │ │ @Dao │ │
│ │ (data model │ │ (queries: │ │
│ │ = table) │ │ insert, │ │
│ └─────────────┘ │ delete, │ │
│ │ update, │ │
│ ┌─────────────┐ │ select) │ │
│ │ @Database │ └──────────────┘ │
│ │ (ties it all│ │
│ │ together) │ │
│ └─────────────┘ │
│ │
└───────────────────────────────────────────────────┘
│
▼
SQLite (actual file on device)
Step 1: Add Dependencies
// In app/build.gradle.kts:
plugins {
id("com.google.devtools.ksp") version "1.9.0-1.0.13" // or kapt
}
dependencies {
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1") // coroutines support
ksp("androidx.room:room-compiler:2.6.1")
}Step 2: Define the Entity (Table)
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String,
val createdAt: Long = System.currentTimeMillis()
)Step 3: Create the DAO (Data Access Object)
import androidx.room.*
import kotlinx.coroutines.flow.Flow
@Dao
interface NoteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(note: Note)
@Delete
suspend fun delete(note: Note)
@Update
suspend fun update(note: Note)
@Query("SELECT * FROM notes ORDER BY createdAt DESC")
fun getAllNotes(): Flow> // returns a live stream of notes
@Query("SELECT * FROM notes WHERE id = :noteId")
suspend fun getNoteById(noteId: Int): Note?
@Query("DELETE FROM notes WHERE id = :noteId")
suspend fun deleteById(noteId: Int)
@Query("SELECT COUNT(*) FROM notes")
suspend fun count(): Int
}
Step 4: Define the Database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile
private var INSTANCE: NoteDatabase? = null
fun getInstance(context: Context): NoteDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java,
"note_database"
).build().also { INSTANCE = it }
}
}
}
}Step 5: Use Room in a ViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.launch
class NoteViewModel(private val dao: NoteDao) : ViewModel() {
val notes: StateFlow> = dao.getAllNotes()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun addNote(title: String, content: String) = viewModelScope.launch {
dao.insert(Note(title = title, content = content))
}
fun deleteNote(note: Note) = viewModelScope.launch {
dao.delete(note)
}
fun updateNote(note: Note) = viewModelScope.launch {
dao.update(note)
}
}
Step 6: Connect in Activity
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val database = NoteDatabase.getInstance(this)
val factory = NoteViewModelFactory(database.noteDao())
viewModel = ViewModelProvider(this, factory)[NoteViewModel::class.java]
// Collect notes and update UI
lifecycleScope.launch {
viewModel.notes.collect { notes ->
// Update RecyclerView adapter with latest notes
}
}
// Add a note
viewModel.addNote("Shopping", "Milk, Bread, Eggs")
}
}Room Query Examples
@Dao
interface ProductDao {
@Query("SELECT * FROM products WHERE price BETWEEN :min AND :max ORDER BY price ASC")
suspend fun getByPriceRange(min: Double, max: Double): List
@Query("SELECT * FROM products WHERE category = :cat AND inStock = 1")
fun getLiveByCategory(cat: String): Flow>
@Query("UPDATE products SET stock = stock - 1 WHERE id = :id")
suspend fun decrementStock(id: Int)
@Query("SELECT SUM(price * qty) FROM cart_items WHERE userId = :uid")
suspend fun getCartTotal(uid: Int): Double?
}
