Local Storage in Android with Room (SQLite)

    Atul Kabra4 min readUpdated

    Local Storage in Android with Room (SQLite)

    Android ships with SQLite, a small relational database stored on the device. You rarely talk to it directly; instead you use Room, the recommended library that sits on top of SQLite and removes most of the boilerplate while catching errors at compile time. To store data with Room you define three things: an entity (a table), a DAO (the queries), and a database class that ties them together. This article shows all three with coroutines.

    When to Use Local Storage

    Use Room when you need structured data that survives app restarts: saved notes, a list of students, cached content, settings that are more than a single value. For one or two simple values (like a theme flag), a lighter option such as DataStore is enough. Room shines when you have rows and queries.

    Step 1: Add Room to Your Project

    In your module's build.gradle, add the Room dependencies and enable the annotation processor (KSP). Sync the project. The official Room documentation lists the current coordinates; add the runtime, the KTX extensions, and the compiler.

    Step 2: Define an Entity

    An entity is a Kotlin data class annotated to become a table.

    import androidx.room.Entity
    import androidx.room.PrimaryKey
    
    // @Entity makes this class a table named "students"
    @Entity(tableName = "students")
    data class Student(
        // each row needs a unique id; autoGenerate lets Room assign it
        @PrimaryKey(autoGenerate = true) val id: Int = 0,
        val name: String,
        val rollNo: Int
    )
    

    Step 3: Define a DAO

    A DAO (Data Access Object) is an interface where you declare your queries. Room generates the implementation. Note the suspend keyword, which lets these run off the main thread with coroutines.

    import androidx.room.Dao
    import androidx.room.Insert
    import androidx.room.Query
    
    @Dao
    interface StudentDao {
        // suspend means this can be called from a coroutine, off the main thread
        @Insert
        suspend fun insert(student: Student)
    
        // a plain SQL query; Room checks it at compile time
        @Query("SELECT * FROM students ORDER BY name")
        suspend fun getAll(): List<Student>
    }
    

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses

    Step 4: Define the Database

    The database class lists the entities and exposes the DAOs.

    import androidx.room.Database
    import androidx.room.RoomDatabase
    
    // list every entity and bump the version when the schema changes
    @Database(entities = [Student::class], version = 1)
    abstract class AppDatabase : RoomDatabase() {
        abstract fun studentDao(): StudentDao
    }
    

    Step 5: Build and Use It

    Create the database once (building it is relatively expensive) and reuse the instance. Then call the DAO from a coroutine.

    import android.content.Context
    import androidx.room.Room
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.withContext
    
    // Build the database once and keep the reference
    fun createDatabase(context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app.db"          // file name on the device
        ).build()
    }
    
    // Example of writing then reading, all off the main thread
    suspend fun saveAndLoad(db: AppDatabase) = withContext(Dispatchers.IO) {
        val dao = db.studentDao()
        dao.insert(Student(name = "Asha", rollNo = 12)) // write
        val all = dao.getAll()                           // read back
        all // returned list of students
    }
    

    Because the DAO functions are suspend and we run them on Dispatchers.IO, the database work stays off the main thread and the UI never freezes.

    Common Mistakes

    • Talking to SQLite directly with raw cursors. It is error-prone. Use Room; it checks your queries at compile time.
    • Running database calls on the main thread. This freezes the UI and can crash the app. Use suspend functions and a background dispatcher.
    • Rebuilding the database on every screen. Building is expensive. Create it once and share the instance.
    • Forgetting to bump the version after changing the schema. Add or rename a column without increasing version and adding a migration, and the app crashes on existing installs.
    • Storing huge blobs in the database. Keep large files on disk and store only their path in Room.

    FAQ

    Is Room the same as SQLite? Room is a layer on top of SQLite. SQLite is the actual database; Room makes it safe and pleasant to use from Kotlin.

    Can Room give me live updates when data changes? Yes, DAO functions can return Flow so your UI updates automatically when the table changes. Start with simple suspend queries first.

    Where is the database file stored? In your app's private storage on the device, isolated from other apps.

    Keep Going

    Often you fetch data from the internet and cache it locally; learn the first half in Calling an API in Android. To handle the storage permissions some apps need, see Permissions in Android. The full path is at the Android learning hub.


    Want to build a real data-driven app with guidance? Join the waitlist for the Android Development course at Infoplanet in Jalgaon.

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses
    Atul Kabra

    Founder, Infoplanet

    Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.

    Related guides