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>