Room in Android – Complete Guide with Kotlin Examples (2026)

Modern Android applications rarely rely only on network APIs. Whether you’re building a note-taking app, e-commerce platform, messaging application, or offline-first solution, you need a reliable local database.

Google recommends Room, part of the Android Jetpack libraries, as the preferred way to work with SQLite databases. Room simplifies database operations, reduces boilerplate code, provides compile-time query validation, and integrates seamlessly with Kotlin Coroutines, Flow, ViewModel, and Jetpack Compose.

In this comprehensive guide, you’ll learn everything you need to know about Room, from basic concepts to advanced architecture patterns.

What is Room?

Room is an ORM (Object Relational Mapping) library built on top of SQLite.

Instead of writing hundreds of lines of SQLite boilerplate code, Room allows developers to interact with databases using Kotlin objects and annotations.

Think of Room as a bridge between your Kotlin classes and SQLite tables.

Without Room:

  • Manual SQL management
  • Cursor handling
  • Boilerplate code
  • Runtime SQL errors

With Room:

  • Annotation-based development
  • Automatic object mapping
  • Compile-time query validation
  • Easy migrations
  • Coroutine support
  • Flow support

Google officially recommends Room over using SQLite APIs directly for most Android applications.

Why Use Room?

Room solves many problems developers face with SQLite.

Advantages

  • Less boilerplate code
  • Type-safe SQL
  • Compile-time query verification
  • Easy database migrations
  • Coroutine support
  • Kotlin Flow support
  • LiveData support
  • Paging integration
  • Works perfectly with MVVM
  • Supports relationships between tables

Room Architecture

Room consists of three primary components.

UI
 │
 ▼
Repository
 │
 ▼
DAO
 │
 ▼
Room Database
 │
 ▼
SQLite

The application interacts with the DAO instead of directly communicating with SQLite.

Core Components of Room

1. Entity

An Entity represents a database table.

Example:

@Entity(tableName = "users")
data class User(

    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,

    val name: String,

    val email: String
)

Each object corresponds to one row in the database.

2. DAO (Data Access Object)

DAO contains all database operations.

Example:

@Dao
interface UserDao {

    @Insert
    suspend fun insert(user: User)

    @Update
    suspend fun update(user: User)

    @Delete
    suspend fun delete(user: User)

    @Query("SELECT * FROM users")
    fun getUsers(): Flow<List<User>>
}

Room automatically generates the implementation during compilation.

3. Database Class

The database class connects everything together.

@Database(
    entities = [User::class],
    version = 1
)
abstract class AppDatabase : RoomDatabase() {

    abstract fun userDao(): UserDao

}

Setting Up Room

Add dependencies.

implementation("androidx.room:room-runtime:<latest-version>")

ksp("androidx.room:room-compiler:<latest-version>")

implementation("androidx.room:room-ktx:<latest-version>")

Google recommends using KSP instead of the older annotation processor for Kotlin projects.

Creating the Database

val database = Room.databaseBuilder(

    applicationContext,

    AppDatabase::class.java,

    "app_database"

).build()

Usually this is provided using Dependency Injection such as Hilt.

CRUD Operations

Insert

userDao.insert(User(name = "John", email = "john@email.com"))

Read

val users = userDao.getUsers()

Update

userDao.update(user)

Delete

userDao.delete(user)

SQL Queries in Room

Room supports full SQLite queries.

Example:

@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User

Example with sorting:

@Query("SELECT * FROM users ORDER BY name ASC")

Filtering:

@Query("SELECT * FROM users WHERE age > :age")

Searching:

@Query("SELECT * FROM users WHERE name LIKE '%' || :keyword || '%'")
Using Coroutines

Database operations should never run on the Main Thread.

Room works naturally with Coroutines.

viewModelScope.launch {

    repository.insert(user)

}

Using Flow

Flow automatically emits updates whenever the database changes.

@Query("SELECT * FROM users")
fun getUsers(): Flow<List<User>>

Advantages:

  • Automatic updates
  • Reactive UI
  • Lifecycle-friendly
  • Perfect for Compose

Room with MVVM

A typical architecture looks like this.

Compose UI

↓

ViewModel

↓

Repository

↓

DAO

↓

Room Database

Each layer has a single responsibility, making the application easier to maintain and test.

Room Relationships

Real applications usually have multiple tables.

Example:

User

↓

Orders

↓

Products

Room supports:

  • One-to-One
  • One-to-Many
  • Many-to-Many

using annotations like:

  • @Relation
  • @Embedded
  • @Junction

Transactions

Sometimes multiple operations must succeed together.

@Transaction
suspend fun transferMoney() {

}

Transactions ensure database consistency.

Indexes

Indexes improve query performance.

@Entity(
    indices = [
        Index("email")
    ]
)

Use indexes on frequently searched columns.

Foreign Keys

Room supports referential integrity.

@Entity(

    foreignKeys = [

        ForeignKey(

            entity = User::class,

            parentColumns = ["id"],

            childColumns = ["userId"]

        )

    ]

)

Migrations

When your schema changes, increase the database version.

Without migrations:

App crashes

With migrations:

Existing user data remains safe.

Example:

val migration = object : Migration(1, 2) {

    override fun migrate(database: SupportSQLiteDatabase) {

        database.execSQL(
            "ALTER TABLE users ADD COLUMN phone TEXT"
        )

    }

}

Then:

Room.databaseBuilder(...)
    .addMigrations(migration)

Room provides structured migration support, helping preserve user data across app updates.

Room + Jetpack Compose

Room integrates naturally with Compose.

Example:

val users by viewModel.users.collectAsState()

Whenever the database changes:

  • Flow emits
  • ViewModel updates
  • Compose recomposes automatically

No manual refresh is needed.

Testing Room

Google recommends using an in-memory database for tests.

Room.inMemoryDatabaseBuilder(...)

Benefits:

  • Fast
  • No disk writes
  • Easy cleanup

Performance Tips

Use Flow

Avoid repeatedly querying the database manually.

Add Indexes

Speed up searches on large tables.

Avoid Main Thread Queries

Always use Coroutines.

Return Only Needed Columns

Instead of:

SELECT *

Use:

SELECT name,email

Common Room Annotations

Annotation Purpose
@Entity Creates table
@Dao Data access
@Insert Insert row
@Update Update row
@Delete Delete row
@Query Custom SQL
@Database Database class
@PrimaryKey Primary key
@Relation Table relationship
@Embedded Nested object
@Transaction Atomic operation

Common Mistakes

Running queries on Main Thread

This causes ANR issues.

Forgetting Migrations

Changing schemas without migrations can crash the app or force destructive recreation.

Using SELECT *

Retrieve only the data you need.

Missing Indexes

Large datasets become slow without proper indexing.

Ignoring Repository Pattern

Accessing DAOs directly from the UI makes applications harder to maintain.

Best Practices

  • Follow MVVM architecture.
  • Use Repository as the single source of truth.
  • Prefer Kotlin Flow over LiveData for new projects.
  • Use Coroutines for background operations.
  • Create migration scripts for every schema change.
  • Add indexes to frequently queried columns.
  • Keep entities focused and normalized.
  • Write unit tests for DAOs and migrations.
  • Inject the database with Hilt or another DI framework.
  • Use Room as the local source of truth in offline-first apps.

Room vs SQLite

Feature Room SQLite
Boilerplate Minimal High
Compile-time SQL validation
Coroutines Manual
Flow support
Type safety Limited
Migrations Built-in Manual
MVVM integration Excellent Manual
Learning curve Easy Moderate

When Should You Use Room?

Room is an excellent choice for:

  • Offline-first applications
  • Notes apps
  • To-do applications
  • Expense trackers
  • Chat applications
  • E-commerce carts
  • Inventory systems
  • Local caching of REST API responses
  • Health and fitness apps
  • Any app requiring persistent structured data

Conclusion

Room has become the standard solution for local data persistence in modern Android development. By abstracting SQLite behind a clean, annotation-driven API, it reduces boilerplate, catches SQL issues at compile time, and integrates seamlessly with Jetpack components such as ViewModel, Kotlin Coroutines, Flow, Paging, and Jetpack Compose.

Whether you’re building a small utility app or a large offline-first application, mastering Room is an essential skill. By following the best practices outlined in this guide—using repositories, handling migrations carefully, leveraging Flow for reactive updates, and structuring your database effectively—you can build scalable, maintainable, and high-performance Android applications.

Frequently Asked Questions (FAQ)

Is Room better than SQLite?

Yes. Room is built on top of SQLite and offers compile-time query validation, less boilerplate, safer APIs, and better integration with modern Android architecture components.

Does Room support Kotlin Coroutines?

Yes. Room has first-class support for Kotlin Coroutines and works seamlessly with suspend functions.

Can Room work with Jetpack Compose?

Absolutely. Room integrates naturally with Kotlin Flow, making it ideal for reactive UI updates in Jetpack Compose.

Is Room suitable for large applications?

Yes. Many production-grade Android applications use Room for local persistence, especially when combined with MVVM, Repository, and Dependency Injection.

Does Room replace SQLite?

No. Room is an abstraction layer built on top of SQLite. It still stores data in an SQLite database while providing a more developer-friendly API.

About Farzad Sarseyfi

Check Also

Toasts in Android

Toasts in Android: A Complete Guide with Sample Code

In Android development, displaying short messages to users is a common requirement. Whether you want …

Leave a Reply

Your email address will not be published. Required fields are marked *