RoomDatabase in android

Local data storage is essential for building modern Android apps that work offline and store data persistently. Whether you’re creating a note-taking app, a budgeting app, or a simple to-do list, you’ll want a reliable and clean way to handle local databases. That’s where Room comes into play.

In this article, we’ll walk through how to implement a Room Database in Kotlin using a practical To-Do List App example and walkthrough all the CRUD (Create,Read,Update,Delete) operations. You’ll learn how to define your database schema, write data access objects (DAOs), and use Room with Kotlin coroutines for a modern Android architecture.

Roomdatabase Architecture image

What is Room Database in Android?

Room is an abstraction layer built on top of SQLite Database. It simplifies database access and provides compile-time verification of SQL queries. It integrates seamlessly with Kotlin, Coroutines, LiveData, and Flow. Room database has three components :

  • Entity: Represents a table in the database.
  • DAO (Data Access Object): Provides methods to interact with the database.
  • Database: A singleton instance that provides access to the SQlite database.

Entity

Entities is Room database represents tables. Each instance of the class corresponds to a row in the table.

				
					@Entity(tableName = "todo_table")
data class TodoItem(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val title: String,
    val description: String,
    val isDone: Boolean = false
)
				
			

DAO (Data Access Object)

DAO is interface with DAO Annotation that is used to interact with database using methods. Inside DAO we define methods (INSERT, UPDATE, DELETE, FETCH) to perform database operations.  

				
					@Dao
interface TodoDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertTodo(todo: TodoItem)

    @Update
    suspend fun updateTodo(todo: TodoItem)

    @Delete
    suspend fun deleteTodo(todo: TodoItem)

    @Query("SELECT * FROM todo_table ORDER BY id DESC")
    suspend fun getAllTodos(): List<TodoItem>
}
				
			

Database

The Database class is the main entry point for interacting with the Room database. Here, we tie everything together.

				
					@Database(entities = [TodoItem::class], version = 1, exportSchema = false)
abstract class TodoDatabase : RoomDatabase() {

    abstract fun todoDao(): TodoDao

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

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

Step by Step Implementation

Here, we have created a small project of ToDoApp Using Room Database in Android as shown below : 

Project Structure

Project structure todo app

STEP 1: Add Required Dependencies

				
					//add in build.gradle(:app)

plugins {

    id 'kotlin-kapt' // ✅ Required for kapt to work
}
				
			
				
					//add in build.gradle(:app)

dependencies {

    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
    implementation "androidx.room:room-runtime:2.6.1"
    implementation "androidx.room:room-ktx:2.6.1"
    kapt "androidx.room:room-compiler:2.6.1"
    implementation "com.google.android.material:material:1.11.0"

}
				
			

STEP 2: Create Entity

File : ToDoItem.kt 

				
					package com.vairagicodes.todoapp.data

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

@Entity(tableName = "todo_table")
data class TodoItem(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val title: String,
    val isDone: Boolean = false
)
				
			

STEP 3: Create DAO

Create a DAO (Data Access Model) for interacting with the Database.
File: TodoDao.kt

				
					package com.vairagicodes.todoapp.data

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update

@Dao
interface TodoDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(todo: TodoItem)

    @Delete
    suspend fun delete(todo: TodoItem)

    @Update
    suspend fun update(todo: TodoItem)

    @Query("SELECT * FROM todo_table ORDER BY id DESC")
    fun getAllTodos(): LiveData<List<TodoItem>>
}
				
			

STEP 4: Create Database Class

File: TodoDatabase.kt

				
					package com.vairagicodes.todoapp.data

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [TodoItem::class], version = 1)
abstract class TodoDatabase : RoomDatabase() {
    abstract fun todoDao(): TodoDao

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

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

STEP 5: Repository Layer

File: TodoRepository.kt

				
					package com.vairagicodes.todoapp.repository

import com.vairagicodes.todoapp.data.TodoDao
import com.vairagicodes.todoapp.data.TodoItem

class TodoRepository(private val dao: TodoDao) {
    val allTodos = dao.getAllTodos()

    suspend fun insert(todo: TodoItem) = dao.insert(todo)
    suspend fun delete(todo: TodoItem) = dao.delete(todo)
    suspend fun update(todo: TodoItem) = dao.update(todo)
}
				
			

STEP 6: ViewModel

File: TodoViewModel.kt

				
					package com.vairagicodes.todoapp.ui

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.vairagicodes.todoapp.data.TodoDatabase
import com.vairagicodes.todoapp.data.TodoItem
import com.vairagicodes.todoapp.repository.TodoRepository
import kotlinx.coroutines.launch

class TodoViewModel(application: Application) : AndroidViewModel(application) {
    private val repository: TodoRepository
    val allTodos: LiveData<List<TodoItem>>

    init {
        val dao = TodoDatabase.getDatabase(application).todoDao()
        repository = TodoRepository(dao)
        allTodos = repository.allTodos
    }

    fun insert(todo: TodoItem) = viewModelScope.launch {
        repository.insert(todo)
    }

    fun delete(todo: TodoItem) = viewModelScope.launch {
        repository.delete(todo)
    }

    fun update(todo: TodoItem) = viewModelScope.launch {
        repository.update(todo)
    }
}
				
			

STEP 7: Create UI for the app

File: activity_main.xml

				
					<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/todoRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:layout_above="@+id/fab"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_alignParentBottom="true" android:layout_alignParentEnd="true"
        android:layout_margin="16dp"
        android:src="@android:drawable/ic_input_add" />
</RelativeLayout>
				
			

File: item_todo.xml

				
					<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="wrap_content"
    android:layout_marginBottom="8dp" android:layout_marginHorizontal="4dp"
    android:elevation="4dp">

    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:padding="16dp" android:orientation="horizontal" android:gravity="center_vertical">

        <TextView android:id="@+id/todoTitle"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="1" android:text="Sample Task"
            android:textSize="16sp" android:textStyle="bold" />


        <CheckBox
            android:id="@+id/checkBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp" />

        <ImageButton android:id="@+id/deleteBtn"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:src="@android:drawable/ic_delete" />
    </LinearLayout>
</androidx.cardview.widget.CardView>
				
			

File: dialog_add_todo.xml

				
					<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="wrap_content"
    android:padding="16dp" android:orientation="vertical">

    <EditText android:id="@+id/editTodoTitle"
        android:layout_width="match_parent" android:layout_height="wrap_content"
        android:hint="Enter Task" android:inputType="text" />
</LinearLayout>
				
			

STEP 8: Create Adapter class for RecyclerView

File: TodoAdapter.kt

				
					package com.vairagicodes.todoapp.ui

import android.graphics.Paint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageButton
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.vairagicodes.todoapp.R
import com.vairagicodes.todoapp.data.TodoItem

class TodoAdapter(
    private val onDeleteClick: (TodoItem) -> Unit,
    private val onCheckboxToggle: (TodoItem) -> Unit
) : ListAdapter<TodoItem, TodoAdapter.TodoViewHolder>(DiffCallback()) {

    inner class TodoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val title = view.findViewById<TextView>(R.id.todoTitle)
        val deleteBtn = view.findViewById<ImageButton>(R.id.deleteBtn)
        val checkBox = view.findViewById<CheckBox>(R.id.checkBox)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_todo, parent, false)
        return TodoViewHolder(view)
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
        val item = getItem(position)
        holder.title.text = item.title
        holder.deleteBtn.setOnClickListener { onDeleteClick(item) }


        holder.checkBox.isChecked = item.isDone

        holder.checkBox.setOnCheckedChangeListener(null) // avoid recycling issue
        holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
            onCheckboxToggle(item.copy(isDone = isChecked))
        }

        if (item.isDone) {
            holder.title.paintFlags = holder.title.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
        } else {
            holder.title.paintFlags = holder.title.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
        }

    }

    class DiffCallback : DiffUtil.ItemCallback<TodoItem>() {
        override fun areItemsTheSame(old: TodoItem, new: TodoItem) = old.id == new.id
        override fun areContentsTheSame(old: TodoItem, new: TodoItem) = old == new
    }
}
				
			

STEP 9: MainActivity Class

File: MainActivity.kt

				
					package com.vairagicodes.todoapp

import android.os.Bundle
import android.view.LayoutInflater
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.vairagicodes.todoapp.data.TodoItem
import com.vairagicodes.todoapp.ui.TodoAdapter
import com.vairagicodes.todoapp.ui.TodoViewModel

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: TodoViewModel
    private lateinit var adapter: TodoAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val recyclerView = findViewById<RecyclerView>(R.id.todoRecyclerView)
        val fab = findViewById<FloatingActionButton>(R.id.fab)

        adapter = TodoAdapter(
            onDeleteClick =
        { todo -> viewModel.delete(todo) },

            onCheckboxToggle = { updatedTodo ->
                viewModel.update(updatedTodo)
            }
        )
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)

        viewModel = ViewModelProvider(this)[TodoViewModel::class.java]
        viewModel.allTodos.observe(this) { todos ->
            adapter.submitList(todos)
        }

        fab.setOnClickListener {
            showAddTodoDialog()
        }
    }

    private fun showAddTodoDialog() {
        val view = LayoutInflater.from(this).inflate(R.layout.dialog_add_todo, null)
        val editText = view.findViewById<EditText>(R.id.editTodoTitle)

        AlertDialog.Builder(this)
            .setTitle("Add Task")
            .setView(view)
            .setPositiveButton("Add") { _, _ ->
                val title = editText.text.toString()
                if (title.isNotBlank()) {
                    viewModel.insert(TodoItem(title = title))
                }
            }
            .setNegativeButton("Cancel", null)
            .show()
    }
}
				
			

If you find this content Helpful feel free to Enroll in our Android Development Course with Kotlin contact us for more information