Room Database Complete Guide: Local Data Persistence in Compose
Room is Android's recommended persistence library for local SQLite database access. It provides an abstraction layer on top of SQLite while maintaining full control over the database.
Core Components
Entity: Define Your Data Model
Entities represent tables in your database:
@Entity(tableName = "tasks")
data class TaskEntity(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val description: String,
val completed: Boolean = false,
val createdAt: Long = System.currentTimeMillis()
)
Use @ColumnInfo for custom column names, @Ignore to exclude fields from persistence.
DAO: Data Access Operations
DAOs define the methods for accessing database operations:
@Dao
interface TaskDao {
@Insert
suspend fun insertTask(task: TaskEntity)
@Update
suspend fun updateTask(task: TaskEntity)
@Delete
suspend fun deleteTask(task: TaskEntity)
// Flow for real-time observation
@Query("SELECT * FROM tasks ORDER BY createdAt DESC")
fun getAllTasksFlow(): Flow<List<TaskEntity>>
// Suspend for one-shot queries
@Query("SELECT * FROM tasks WHERE id = :id")
suspend fun getTaskById(id: Int): TaskEntity?
}
Key distinction:
- Use
Flow<T>for continuous observation of data changes - Use
suspend funfor one-shot queries that don't need observation - Room automatically manages threading
Database: Singleton Setup
Create an abstract database class:
@Database(
entities = [TaskEntity::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database.db"
)
.build()
.also { INSTANCE = it }
}
}
}
}
Always use a singleton pattern to prevent multiple database instances.
Repository Pattern
Repositories abstract data access logic from the UI layer:
class TaskRepository(
private val taskDao: TaskDao
) {
val allTasks: Flow<List<TaskEntity>> = taskDao.getAllTasksFlow()
suspend fun addTask(task: TaskEntity) {
taskDao.insertTask(task)
}
suspend fun updateTask(task: TaskEntity) {
taskDao.updateTask(task)
}
suspend fun getTask(id: Int): TaskEntity? {
return taskDao.getTaskById(id)
}
}
ViewModel Integration
Connect your repository to UI through ViewModels:
@HiltViewModel
class TaskViewModel @Inject constructor(
private val repository: TaskRepository
) : ViewModel() {
val tasks: StateFlow<List<TaskEntity>> = repository.allTasks
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun addTask(title: String, description: String) {
viewModelScope.launch {
repository.addTask(
TaskEntity(title = title, description = description)
)
}
}
}
Migrations
Handle schema changes with migrations:
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"ALTER TABLE tasks ADD COLUMN priority INTEGER DEFAULT 0"
)
}
}
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2)
.build()
TypeConverters
Convert complex types to/from database-compatible types:
class DateTypeConverter {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
@Database(entities = [TaskEntity::class], version = 1)
@TypeConverters(DateTypeConverter::class)
abstract class AppDatabase : RoomDatabase() {
// ...
}
KSP Setup
Configure Room's Kotlin Symbol Processing for annotation processing:
# build.gradle.kts
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation("androidx.room:room-runtime:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
}
KSP is faster than KAPT and is now recommended by Google.
Best Practices
- Single Database Instance: Use companion object and lazy initialization
- Flow for Observation: Always use Flow for queries that UI observes
- Suspend for One-Shots: Use suspend fun for single-value queries
- Repository Pattern: Don't expose DAO directly to UI
-
Testing: Use
createInMemoryDatabaseBuilder()for unit tests -
Schema Export: Set
exportSchema = trueand commit schemas for version control
8 Android App Templates → https://myougatheaxo.gumroad.com
Top comments (0)