Implementing CRUD Operations with Android Room Database

Dependencies

Add the following to the app-level build.gradle file:

dependencies {
    implementation("androidx.room:room-runtime:2.6.1")
    annotationProcessor("androidx.room:room-compiler:2.6.1")
}

Data Entity

package com.example.app

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey

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

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "user_id", typeAffinity = ColumnInfo.INTEGER)
    var id: Int = 0

    @ColumnInfo(name = "user_name", typeAffinity = ColumnInfo.TEXT)
    var name: String = ""

    @ColumnInfo(name = "user_age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0

    constructor(id: Int, name: String, age: Int) {
        this.id = id
        this.name = name
        this.age = age
    }

    @Ignore
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    @Ignore
    constructor(id: Int) {
        this.id = id
    }
}

The @Ignore annotation tells Room to exclude the annotated constructro or field from database operations. This is useful when you need multiple constructors for different use cases but only want certain ones to persist to the database.

Data Access Object (DAO)

package com.example.app

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update

@Dao
interface UserDao {

    @Insert
    fun insert(vararg users: User)

    @Delete
    fun delete(vararg users: User)

    @Update
    fun update(vararg users: User)

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

    @Query("SELECT * FROM users WHERE user_id = :id")
    fun getUserById(id: Int): User
}

The DAO defines all database operations as abstract methods. Room automatically generates the implementation at compile time based on the annotations.

Database Class

package com.example.app

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

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

    companion object {
        private const val DB_NAME = "app_database.db"
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                INSTANCE ?: Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    DB_NAME
                ).build().also { INSTANCE = it }
            }
        }
    }

    abstract fun userDao(): UserDao
}

The database class must be abstract and extend RoomDatabase. Room generates the implementation for the abstract DAO methods automatically.

AsyncTask Wrappers

package com.example.app

import android.os.AsyncTask
import androidx.loader.content.AsyncTaskLoader

class InsertUserTask(private val dao: UserDao) : AsyncTask<User, Void, Void>() {
    override fun doInBackground(vararg users: User): Void? {
        dao.insert(*users)
        return null
    }
}

class RemoveUserTask(private val dao: UserDao) : AsyncTask<User, Void, Void>() {
    override fun doInBackground(vararg users: User): Void? {
        dao.delete(*users)
        return null
    }
}

class ModifyUserTask(private val dao: UserDao) : AsyncTask<User, Void, Void>() {
    override fun doInBackground(vararg users: User): Void? {
        dao.update(*users)
        return null
    }
}

class FetchUsersTask(
    private val dao: UserDao,
    private val callback: (List<User>) -> Unit
) : AsyncTask<Void, Void, Void>() {

    override fun doInBackground(vararg voids: Void): Void? {
        val result = dao.getAllUsers()
        callback(result)
        return null
    }
}

All database operations should be performed on a background thread to prevent blocking the main UI thread.

RecyclerView Adapter

package com.example.app

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class UserListAdapter(private var userList: List<User>) :
    RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {

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

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = userList[position]
        holder.bind(user)
    }

    override fun getItemCount(): Int = userList.size

    fun updateData(newList: List<User>) {
        userList = newList
        notifyDataSetChanged()
    }

    class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val idText: TextView = itemView.findViewById(R.id.textId)
        private val nameText: TextView = itemView.findViewById(R.id.textName)
        private val ageText: TextView = itemView.findViewById(R.id.textAge)

        fun bind(user: User) {
            idText.text = user.id.toString()
            nameText.text = user.name
            ageText.text = user.age.toString()
        }
    }
}

Main Activity

package com.example.app

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    private lateinit var userDao: UserDao
    private lateinit var adapter: UserListAdapter

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

        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        adapter = UserListAdapter(emptyList())
        recyclerView.adapter = adapter

        val database = AppDatabase.getInstance(this)
        userDao = database.userDao()
    }

    fun addUser(view: View) {
        val user1 = User("Alice", 22)
        val user2 = User("Bob", 19)
        InsertUserTask(userDao).execute(user1, user2)
    }

    fun removeUser(view: View) {
        val user = User(2)
        RemoveUserTask(userDao).execute(user)
    }

    fun modifyUser(view: View) {
        val user = User(3, "Charlie", 25)
        ModifyUserTask(userDao).execute(user)
    }

    fun loadUsers(view: View) {
        FetchUsersTask(userDao) { users ->
            runOnUiThread {
                adapter.updateData(users)
            }
        }.execute()
    }
}

Layouts

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineTop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.15" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineMiddle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.08" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineCenter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <Button
        android:id="@+id/btnAdd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Add"
        android:onClick="addUser"
        app:layout_constraintBottom_toTopOf="@id/guidelineMiddle"
        app:layout_constraintEnd_toStartOf="@id/guidelineCenter"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnDelete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete"
        android:onClick="removeUser"
        app:layout_constraintBottom_toTopOf="@id/guidelineMiddle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/guidelineCenter"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnUpdate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Update"
        android:onClick="modifyUser"
        app:layout_constraintBottom_toTopOf="@id/guidelineTop"
        app:layout_constraintEnd_toStartOf="@id/guidelineCenter"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/guidelineMiddle" />

    <Button
        android:id="@+id/btnQuery"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Query"
        android:onClick="loadUsers"
        app:layout_constraintBottom_toTopOf="@id/guidelineTop"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/guidelineCenter"
        app:layout_constraintTop_toTopOf="@id/guidelineMiddle" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/guidelineTop" />

</androidx.constraintlayout.widget.ConstraintLayout>

user_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingVertical="12dp">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guide1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.2" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guide2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.8" />

    <TextView
        android:id="@+id/textId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/guide1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/guide2"
        app:layout_constraintStart_toEndOf="@id/guide1"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textAge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/guide2"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Tags: Android Room Database Jetpack CRUD Operations kotlin

Posted on Wed, 20 May 2026 17:48:23 +0000 by cbyrns1125