Introduction
When working with Room, you might have run into a situation where you only want to update specific fields of an entity instead of replacing the entire row. In this tutorial, we will learn how to do this in two different ways.
Goals
At the end of the tutorial, you would have learned:
- How to update individual fields of an entity.
Tools Required
- Android Studio. The version used in this tutorial is Android Studio Chipmunk 2021.2.1.
Prerequisite Knowledge
- Intermediate Android.
- Room database.
Project Setup
To follow along with the tutorial, perform the steps below:
-
Create a new Android project with the default Empty Activity.
-
Add the dependencies for Room in the module build.gradle file.
def room_version = "2.4.2" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
-
In the same file, add the kapt plugin under plugins.
id 'kotlin-kapt'
-
Create a new data class called User.kt. This class will be used as an entity.
@Entity data class User( @PrimaryKey val id: Int = 1, val name: String, val age: Int )
-
Create a new Dao for the User entity using the code below.
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(vararg users: User) @Update fun update(vararg users: User) @Delete fun delete(vararg users: User) }
-
Create a MyDatabase class using the code below.
@Database(entities = [User::class], version = 1) abstract class MyDatabase : RoomDatabase() { abstract fun studentDao(): UserDao }
-
Replace the content of activity_main.xml with the code below.
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button_update" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/update" app:layout_constraintBottom_toTopOf="@+id/button_partialUpdate" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button_partialUpdate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/partial_update" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button_update" /> </androidx.constraintlayout.widget.ConstraintLayout>
-
Add the string resources below into strings.xml.
<string name="update">Update</string> <string name="partial_update">Partial Update</string>
-
In
MainActivity#onCreate()
, append the code below to insert an entity at launch. Note that the database used here is an in-memory database and will lose data after the app is killed.//Creates db and get a Dao instance val studentDao = Room.inMemoryDatabaseBuilder( applicationContext, MyDatabase::class.java ).build() .studentDao() //Insert a User on start lifecycleScope.launch(Dispatchers.IO) { val user = User( name = "Anna", age = 50 ) studentDao.insert(user) } val updateButton = findViewById<Button>(R.id.button_update) updateButton.setOnClickListener { //Updates the entire row where id = 1 lifecycleScope.launch(Dispatchers.IO){ val user = User( name = "Updated Anna", age = 51 ) studentDao.update(user) } }
Project Overview
The tutorial project contains two buttons.
The Update button will update update the entire User row when clicked.
The Partial Update button does nothing as of now. At the end of the tutorial, it should be able to perform a partial update to the User with an ID of 1. Besides the id
column, the User entity contains two other columns, name and age. These are the columns that we want to individually update.
Partial update with @Query
The first way to update just a single field in an entity is to simply use an @Query
Dao function with an UPDATE
statement.
In UserDao.kt, add the function below to update just the column name
using a @Query
.
@Query("UPDATE user SET name=:name WHERE id=:id")
fun updateNameByQuery(id: Int, name: String)
Unless you want to, we don’t really have to write any UI code to test this. You can just use the Database Inspector like in the animation below.
Partial update using partial entity
Personally, I believe that we should only use @Query
with a non-querying statement only as a last resort. Fortunately, Room’s @Update
supports partial updates as well, but some boilerplate is required because we have to create new classes that represent the partial data. Follow the steps below to add code that will update the fields name
and age
individually.
-
Inside of User.kt, add two more data classes to act as partial entities UserName and UserAge. They need to have the
id
field declared as well which can tell Room which row to update.class UserName( val id: Int = 1, val name: String ) class UserAge( val id: Int = 1, val age: Int )
-
Back in UserDao.kt, add the Dao functions below. Each
@Update
annotation needs to pass in a value for theentity
parameter, which is the Class object of the full entity. In the function parameters is where you need to declare the partial entities.@Update(entity = User::class) fun updateName(userName: UserName) @Update(entity = User::class) fun updateAge(userAge: UserAge)
-
To test the new dao functions, we need to the append the code below to
MainActivity#onCreate()
. I have only used the partial partial entity UserName here to keep things simple.val updatePartialButton = findViewById<Button>(R.id.button_partialUpdate) updatePartialButton.setOnClickListener { lifecycleScope.launch(Dispatchers.IO){ val userName = UserName( name = "New Anna" ) studentDao.updateName(userName) } }
When we launch the app, we can see that only the user name is updated after clicking on Partial Update.
Solution Code
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Creates db and get a Dao instance
val studentDao = Room.inMemoryDatabaseBuilder(
applicationContext,
MyDatabase::class.java
).build()
.studentDao()
//Insert a User on start
lifecycleScope.launch(Dispatchers.IO) {
val user = User(
name = "Anna",
age = 50
)
studentDao.insert(user)
}
val updateButton = findViewById<Button>(R.id.button_update)
updateButton.setOnClickListener {
//Updates the entire row where id = 1
lifecycleScope.launch(Dispatchers.IO){
val user = User(
name = "Updated Anna",
age = 51
)
studentDao.update(user)
}
}
val updatePartialButton = findViewById<Button>(R.id.button_partialUpdate)
updatePartialButton.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO){
val userName = UserName(
name = "New Anna"
)
studentDao.updateName(userName)
}
}
}
}
User.kt
@Entity
data class User(
@PrimaryKey val id: Int = 1,
val name: String,
val age: Int
)
class UserName(
val id: Int = 1,
val name: String
)
class UserAge(
val id: Int = 1,
val age: Int
)
UserDao.kt
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(vararg users: User)
@Update
fun update(vararg users: User)
@Delete
fun delete(vararg users: User)
@Query("UPDATE user SET name=:name WHERE id=:id")
fun updateNameByQuery(id: Int, name: String)
@Update(entity = User::class)
fun updateName(userName: UserName)
@Update(entity = User::class)
fun updateAge(userAge: UserAge)
}
MyDatabase.kt
@Database(entities = [User::class], version = 1)
abstract class MyDatabase : RoomDatabase() {
abstract fun studentDao(): UserDao
}
activitiy_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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/update"
app:layout_constraintBottom_toTopOf="@+id/button_partialUpdate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_partialUpdate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/partial_update"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_update" />
</androidx.constraintlayout.widget.ConstraintLayout>
strings.xml
<resources>
<string name="app_name">Daniweb Android Partial Room Entity Update</string>
<string name="update">Update</string>
<string name="partial_update">Partial Update</string>
</resources>
Module build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.daniwebandroidpartialroomentityupdate"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Summary
We have learned how to perform a partial update on an entity in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebAndroidPartialRoomEntityUpdate.