Devesh Rx Logo
Devesh Rx Blog

Android Room Database: Complete CRUD Tutorial with Kotlin

May 7, 2026

Android Room Database: Complete CRUD Tutorial with Kotlin

Raw SQLite in Android is verbose, error-prone, and offers zero compile-time query validation. One typo in a query string and your app crashes at runtime — and your users bear the cost.

That pain is exactly why Google built the Room Persistence Library. Room wraps SQLite in a clean, annotation-driven API, catches query errors at compile time, and plugs directly into Kotlin coroutines and Jetpack Compose.

In this tutorial you’ll build a working Flower App that demonstrates every CRUD operation — Create, Read, Update, and Delete — using Room, Kotlin, and Jetpack Compose.


What is Android Room Database — and why does it matter?

Room is an abstraction layer over SQLite that ships as part of Android’s Jetpack architecture components. It handles all the boilerplate that raw SQLite demands, while still giving you the full power of SQL queries where you need them.

The key advantage over plain SQLite: Room validates your SQL queries at compile time. Ship broken queries only to your local build — never to production.

The 3 core components of Room

  • Entity — a Kotlin data class annotated with @Entity. Each class maps to one table in the database.
  • DAO (Data Access Object) — an interface that defines how you read and write data, using annotations like @Insert, @Query, @Update, and @Delete.
  • Database — the entry point that connects your app to the underlying SQLite file and exposes your DAOs.

How to add Room to your Android project

Open your build.gradle.kts (Module: app) and add the following. We use KSP (Kotlin Symbol Processing) instead of KAPT — it’s significantly faster for annotation processing.

Setting up KSP and dependencies

plugins {
    id("com.google.devtools.ksp") version "2.3.7"
}

dependencies {
    val room_version = "2.8.4"

    implementation("androidx.room:room-runtime:$room_version")
    implementation("androidx.room:room-ktx:$room_version")
    ksp("androidx.room:room-compiler:$room_version")
}

Tip: Sync your Gradle file after adding these. If you’re using an older version of the Room library, check the official Jetpack release notes for the latest stable version.


Step 1 — Define your Entity

An Entity tells Room what your table looks like. Create a file called Flower.kt and annotate the data class with @Entity.

@Entity
data class Flower(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    var name: String,
    var color: String
)

@PrimaryKey(autoGenerate = true) means Room handles ID assignment automatically — you never need to set this field manually when inserting new records.


Step 2 — Create the DAO

The DAO is where all your database interaction logic lives. Create FlowerDao.kt as an interface annotated with @Dao.

@Dao
interface FlowerDao {
    @Insert
    suspend fun insertFlower(flower: Flower)

    @Query("SELECT * FROM Flower")
    fun getAllFlowers(): Flow<List<Flower>>

    @Update
    suspend fun updateFlower(flower: Flower)

    @Query("DELETE FROM Flower WHERE id = :id")
    suspend fun deleteFlowerById(id: Int)
}

Why use suspend and Flow?

Database reads and writes are I/O operations — they block the main thread if run synchronously. suspend marks a function as a coroutine, which moves the work off the UI thread automatically.

Flow is the reactive piece of the puzzle. When you return a Flow from a query, Room watches the table and emits a fresh list every time the data changes. Your UI stays in sync with zero extra polling code.


Step 3 — Build the Room Database class

Create FlowerDatabase.kt. This class ties your Entity and DAO together and acts as the single access point to your database file.

@Database(entities = [Flower::class], version = 1, exportSchema = false)
abstract class FlowerDatabase : RoomDatabase() {
    abstract fun flowerDao(): FlowerDao

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

        fun getDatabase(context: Context): FlowerDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    FlowerDatabase::class.java,
                    "flower_db"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

The Singleton pattern explained

Creating a new database connection on every call is expensive and causes race conditions. The Singleton pattern ensures only one instance of the database exists throughout the app’s lifecycle. @Volatile guarantees that changes to INSTANCE are immediately visible across all threads, and synchronized prevents two threads from initialising the database simultaneously.


Step 4 — Implementing CRUD in Jetpack Compose

Now that your database layer is ready, connect it to your Compose UI. Each operation below maps directly to one of the four CRUD actions.

Create: inserting data

Launch a coroutine inside a Button’s onClick handler and call insertFlower.

val scope = rememberCoroutineScope()

Button(onClick = {
    scope.launch {
        val flower = Flower(name = name, color = color)
        flowerDao.insertFlower(flower)
    }
}) {
    Text("Add Flower")
}

Read: displaying live data

Collect the Flow as Compose state using collectAsState. The list updates automatically every time the database changes — no manual refresh needed.

val flowers by flowerDao.getAllFlowers().collectAsState(initial = emptyList())

LazyColumn {
    items(flowers) { flower ->
        Text("${flower.name}${flower.color}")
    }
}

Update: modifying records

Pass in the full Flower object with the same id as the record you want to change. Room matches on the primary key.

Button(onClick = {
    scope.launch {
        val updated = Flower(id = id, name = nameInput, color = colorInput)
        flowerDao.updateFlower(updated)
    }
}) {
    Text("Update Flower")
}

Delete: removing records

Use the custom query DAO method to delete by ID — more precise than passing a full object when you only have the ID available.

Button(onClick = {
    coroutineScope.launch {
        database.flowerDao().deleteFlowerById(id)
    }
}) {
    Text("Delete")
}

Frequently Asked Questions about Room Database

What is the difference between Room and SQLite in Android?

SQLite is the underlying database engine built into Android. Room is a Jetpack library that sits on top of SQLite, adding compile-time query validation, annotation-based table mapping, and seamless integration with Kotlin coroutines and Flow. You still get the full power of SQL, but without the boilerplate cursor management and runtime query errors.

Is Room Database suitable for large-scale Android apps?

Yes. Room scales well when combined with the Repository pattern and ViewModel — which separate database access from the UI layer. For very large datasets, Room also supports Paging 3 integration, which loads data in chunks rather than all at once, keeping memory usage in check.

Can I use Room Database with Jetpack Compose?

Absolutely — Room and Jetpack Compose are designed to work together. Room returns data as a Kotlin Flow, which you collect as Compose state using collectAsState(). Any change to the database automatically triggers a recomposition of the affected UI, so your screen always reflects the latest data without any manual update logic.


📌 Full Course Playlist https://www.youtube.com/playlist?list=PLO1OrQEU0vHNmD9Xqzs-qXwzzwrDvdhVu

#Tutorial
0 Introduction
1 Setting up Android Studio IDE
2 Mastering Android Studio: Navigating the IDE & Project Structure
3 Android Activity & Lifecycle Explained
4 Android Services: Background, Foreground, and Bound Services Explained
5 Android Broadcast Receivers: The Complete Guide to Listening and Sending Events
6 Android Content Provider API Tutorial: Access User Data Safely (Kotlin)
7 How to Build UI with Jetpack Compose: A Beginner’s Guide
8 Android Runtime Permissions in Kotlin and Jetpack Compose: Step-by-Step Guide
9 Android Intents Guide: Master Screen Navigation and Data Sharing
10 Android Room Database: Complete CRUD Tutorial with Kotlin

~ ~ THANK YOU FOR READING ~ ~

Share: