android Room Database Kotlin Flow Android Local Storage Jetpack Beginner

Room Database with Flow in Android — A Beginner's Guide (Kotlin)

Store, observe, and react to local data in real time

Room Database with Flow in Android — A Beginner's Guide (Kotlin)

If you've ever built an Android app that needs to save data — like a to-do list, notes, or user settings — you need a local database. Room is the official Android library for this, and when you combine it with Flow, your UI updates automatically whenever data changes. No manual refreshing. No extra code. Just reactive, clean architecture.

In this guide, you'll go from zero to a working Room + Flow setup in Kotlin.

---

What is Room Database?

Room is a library built on top of SQLite. SQLite is powerful but writing raw SQL queries in Android is messy and error-prone. Room gives you:

A clean way to define your database tables using Kotlin classes

Auto-generated SQL under the hood

Compile-time checks so you catch errors before running the app

Think of it as a smart wrapper around SQLite.

---

What is Flow?

Flow is a Kotlin feature that lets you observe a stream of data over time. When combined with Room, it means: whenever your database changes, your UI gets notified automatically.

Without Flow, you'd have to manually query the database every time. With Flow, Room does that for you.

---

Setting Up Room in Your Project

Add these dependencies to your build.gradle (app):

groovybuild.gradle
dependencies {
    val room_version = "2.6.1"
    implementation("androidx.room:room-runtime:$room_version")
    implementation("androidx.room:room-ktx:$room_version")
    kapt("androidx.room:room-compiler:$room_version")
}

Also add the kapt plugin at the top of your build.gradle:

groovy
plugins {
    id("kotlin-kapt")
}
💡 Sync your project after adding dependencies before moving to the next step.

---

Step 1 — Create an Entity (Your Table)

An Entity is just a Kotlin data class that represents a table in your database. Each variable becomes a column.

kotlinNote.kt
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
)

Here we're creating a notes table with three columns: id, title, and content. The id is auto-generated so you don't have to set it manually.

---

Step 2 — Create a DAO (Your Queries)

DAO stands for Data Access Object. This is where you write your database operations.

kotlinNoteDao.kt
import androidx.room.*
import kotlinx.coroutines.flow.Flow

@Dao
interface NoteDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertNote(note: Note)

    @Delete
    suspend fun deleteNote(note: Note)

    @Query("SELECT * FROM notes ORDER BY id DESC")
    fun getAllNotes(): Flow<List<Note>>
}
⚠️ The suspend keyword on insert and delete means they run on a background thread automatically. Never call these from the main thread.

---

Step 3 — Create the Database

kotlinNoteDatabase.kt
import androidx.room.Database
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 getDatabase(context: Context): NoteDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    NoteDatabase::class.java,
                    "note_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

The companion object ensures only one instance of the database exists throughout the app — this is called the Singleton pattern.

---

Step 4 — Observe Data in Your ViewModel

kotlinNoteViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

class NoteViewModel(private val dao: NoteDao) : ViewModel() {

    val notes = dao.getAllNotes()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    fun addNote(title: String, content: String) {
        viewModelScope.launch {
            dao.insertNote(Note(title = title, content = content))
        }
    }

    fun deleteNote(note: Note) {
        viewModelScope.launch {
            dao.deleteNote(note)
        }
    }
}

stateIn converts the Flow into a StateFlow which is safer to collect in the UI.

---

Step 5 — Collect in Your Activity or Fragment

kotlin
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.notes.collect { noteList ->
            adapter.submitList(noteList)
        }
    }
}

Every time you add or delete a note, this block runs automatically with the updated list.

---

Common Mistakes Beginners Make

1. Running database operations on the main thread

⚠️ Never run Room operations on the main thread. Your app will crash with an IllegalStateException. Always use suspend functions inside viewModelScope.launch.

2. Creating multiple database instances

Always use the Singleton pattern shown above. Multiple instances can cause data conflicts.

3. Forgetting room-ktx dependency

🚨 Without room-ktx, Flow support will not work. Double check your build.gradle if you see unresolved Flow references.

---

What's Next?

Add a Repository layer between DAO and ViewModel for cleaner architecture

Use @TypeConverter to store complex objects like lists or dates

Explore Room migrations for when you update your database schema

---

Summary

@Entity — defines your database table

@Dao — contains your queries

@Database — connects everything together

Flow — automatically updates UI on data change

ViewModel — holds and exposes data to the UI

Room + Flow is one of the most practical combinations in Android development. Once you understand this pattern, building offline-first apps becomes much easier.

Asif Rahman
Asif Rahman

Indie Product Engineer focused on toolcraft — building free tools that just work.

← Back to Blog Try Free Tools ⚡