Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
💾 Data, Network & Architecture  ·  Lesson 30

Room Database (SQLite)

What is Room?

Room is Jetpack's library on top of SQLite, the database built into every Android phone. You describe tables with Kotlin classes and queries with annotations; Room checks your SQL at compile time and generates the boring code.

UI (Fragment)observes dataViewModelcalls repositoryRepositorysingle source of truthDAO (@Dao)SQL queriesRoom database → SQLite filestudent_diary.db
Where Room sits in the app
notes🔑 id INTEGERtitle TEXTcontent TEXTsubject TEXTpriority INTEGERdueAt INTEGER?isDone INTEGERisFavorite INTEGERimagePath TEXT?createdAt INTEGER
Student Diary table

Step 1: Dependencies (KSP)

TOML + Kotlin DSL
# gradle/libs.versions.toml
ksp = "2.0.21-1.0.28"
room = "2.6.1"
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }        # under [plugins]

// app/build.gradle.kts
plugins { alias(libs.plugins.ksp) }
dependencies {
    implementation(libs.androidx.room.runtime)
    implementation(libs.androidx.room.ktx)       // coroutines + Flow support
    ksp(libs.androidx.room.compiler)             // generates the DAO code
}

Step 2: Entity = table

Kotlin – data/Note.kt
package com.codingeasily.studentdiary.data

import androidx.room.Entity
import androidx.room.PrimaryKey

/** One row of the "notes" table. */
@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val title: String,
    val content: String = "",
    val subject: String = "General",
    val priority: Int = PRIORITY_MEDIUM,      // 0 = low, 1 = medium, 2 = high
    val dueAt: Long? = null,                  // reminder time in millis, null = no reminder
    val isDone: Boolean = false,
    val isFavorite: Boolean = false,
    val imagePath: String? = null,            // photo copied into app storage
    val createdAt: Long = System.currentTimeMillis()
) {
    companion object {
        const val PRIORITY_LOW = 0
        const val PRIORITY_MEDIUM = 1
        const val PRIORITY_HIGH = 2
    }
}

/** Result of a GROUP BY query: how many notes each subject has. */
data class SubjectCount(val subject: String, val total: Int)
SQL – the table Room creates
-- What Room creates for the Note entity
CREATE TABLE notes (
  id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  subject TEXT NOT NULL,
  priority INTEGER NOT NULL,
  dueAt INTEGER,                -- Long? → nullable
  isDone INTEGER NOT NULL,      -- Boolean → 0/1
  isFavorite INTEGER NOT NULL,
  imagePath TEXT,
  createdAt INTEGER NOT NULL
);

Step 3: DAO = queries

Kotlin – data/NoteDao.kt
package com.codingeasily.studentdiary.data

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow

/** All SQL for the notes table lives here. Room generates the implementation. */
@Dao
interface NoteDao {

    // Flow = the UI is updated automatically whenever the table changes
    @Query("SELECT * FROM notes ORDER BY isDone ASC, priority DESC, createdAt DESC")
    fun getAll(): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE isDone = :done ORDER BY priority DESC, createdAt DESC")
    fun getByDone(done: Boolean): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE isFavorite = 1 ORDER BY createdAt DESC")
    fun getFavorites(): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE subject = :subject ORDER BY createdAt DESC")
    fun getBySubject(subject: String): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE title LIKE '%' || :q || '%' OR content LIKE '%' || :q || '%' ORDER BY createdAt DESC")
    fun search(q: String): Flow<List<Note>>

    @Query("SELECT subject, COUNT(*) AS total FROM notes GROUP BY subject ORDER BY total DESC")
    fun subjectCounts(): Flow<List<SubjectCount>>

    @Query("SELECT COUNT(*) FROM notes")
    fun countAll(): Flow<Int>

    @Query("SELECT COUNT(*) FROM notes WHERE isDone = 1")
    fun countDone(): Flow<Int>

    @Query("SELECT * FROM notes WHERE id = :id")
    suspend fun getById(id: Long): Note?

    @Insert
    suspend fun insert(note: Note): Long

    @Update
    suspend fun update(note: Note)

    @Delete
    suspend fun delete(note: Note)

    @Query("DELETE FROM notes WHERE isDone = 1")
    suspend fun deleteDone(): Int
}
Return typeMeaning
Flow<List<Note>>Observed: emits again on every change (no suspend)
suspend fun … : Note?One-time read inside a coroutine
suspend fun insert(…): LongReturns the new row id
suspend fun deleteDone(): IntReturns number of deleted rows

Step 4: Database class

Kotlin – data/AppDatabase.kt
package com.codingeasily.studentdiary.data

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 AppDatabase : RoomDatabase() {

    abstract fun noteDao(): NoteDao

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

        /** Singleton: the whole app shares one database connection. */
        fun get(context: Context): AppDatabase =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "student_diary.db"
                ).build().also { INSTANCE = it }
            }
    }
}

Step 5: Repository

Kotlin – data/NoteRepository.kt
package com.codingeasily.studentdiary.data

import kotlinx.coroutines.flow.Flow

/**
 * The single source of truth for notes. ViewModels talk to the repository,
 * never directly to the DAO. Later you could add a network source here.
 */
class NoteRepository(private val dao: NoteDao) {
    val allNotes: Flow<List<Note>> = dao.getAll()
    val pendingNotes: Flow<List<Note>> = dao.getByDone(false)
    val doneNotes: Flow<List<Note>> = dao.getByDone(true)
    val favorites: Flow<List<Note>> = dao.getFavorites()
    val subjectCounts: Flow<List<SubjectCount>> = dao.subjectCounts()
    val countAll: Flow<Int> = dao.countAll()
    val countDone: Flow<Int> = dao.countDone()

    fun bySubject(subject: String) = dao.getBySubject(subject)
    fun search(q: String) = dao.search(q)

    suspend fun get(id: Long) = dao.getById(id)
    suspend fun save(note: Note): Long =
        if (note.id == 0L) dao.insert(note) else { dao.update(note); note.id }
    suspend fun delete(note: Note) = dao.delete(note)
    suspend fun deleteDone() = dao.deleteDone()
}

Using it from the UI

Kotlin
// Quick test inside an Activity (later this moves into a ViewModel)
val dao = AppDatabase.get(this).noteDao()

lifecycleScope.launch {
    dao.insert(Note(title = "Maths homework", subject = "Maths", priority = Note.PRIORITY_HIGH))
}

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        dao.getAll().collect { notes ->               // runs again after every change
            adapter.submitList(notes)
        }
    }
}
FragmentDAOSQLitecollect getAll()SELECT …list of 3 notesinsert(new note) (coroutine)INSERTFlow emits list of 4 notes automatically
Insert once — every observer updates

See your data: App Inspection

Run the app, then open View → Tool Windows → App Inspection → Database Inspector. You can browse the notes table and even run SQL queries live.

Changing the table (migrations)

Kotlin
// Version 2 adds a "color" column to notes
@Database(entities = [Note::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() { /* … */ }

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE notes ADD COLUMN color INTEGER NOT NULL DEFAULT 0")
    }
}

Room.databaseBuilder(context, AppDatabase::class.java, "student_diary.db")
    .addMigrations(MIGRATION_1_2)
    .build()

// While developing only (deletes all data!):  .fallbackToDestructiveMigration()

Common mistakes

  • Calling a non-suspend DAO function on the main thread → "Cannot access database on the main thread".
  • Changing the Entity without increasing version → crash "Room cannot verify the data integrity".
  • Building a new database instance in every screen → memory waste; use the singleton.
  • Typos in column names inside @Query → Room shows a compile error (that's a good thing — read it).

Summary

  • @Entity (table) + @Dao (queries) + @Database (singleton) + Repository.
  • Flow for lists that should auto-update; suspend for one-off reads/writes.
  • Increase the version and add a Migration when the schema changes.
✅ Practice

Create a Expense(id, title, amount, category, date) table with queries for all expenses (Flow), total per category (GROUP BY) and delete. Show the list in a RecyclerView that updates after each insert.

Frequently Asked Questions

Why can't I run Room queries on the main thread?
Database work can take time and would freeze the UI. Room throws an exception for main-thread access. Use suspend functions in coroutines or return Flow.
What is the benefit of returning Flow from a DAO?
Room re-runs the query and emits a new list whenever the table changes, so your RecyclerView updates automatically after insert, update or delete.
KSP or kapt for Room?
Use KSP. It is faster and the recommended processor for Room with Kotlin; kapt is in maintenance mode.
← Android Tutorial (Kotlin)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicMVVM: ViewModel, LiveData & Flow Your progress: 30 / 43 (70%) · Lesson 31 / 43 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free