Android Native - How to partially update a Room entity

dimitrilc 2 Tallied Votes 2K Views Share

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:

  1. How to update individual fields of an entity.

Tools Required

  1. Android Studio. The version used in this tutorial is Android Studio Chipmunk 2021.2.1.

Prerequisite Knowledge

  1. Intermediate Android.
  2. Room database.

Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Android project with the default Empty Activity.

  2. 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'
  3. In the same file, add the kapt plugin under plugins.

     id 'kotlin-kapt'
  4. 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
     )
  5. 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)
     }
  6. Create a MyDatabase class using the code below.

     @Database(entities = [User::class], version = 1)
     abstract class MyDatabase : RoomDatabase() {
        abstract fun studentDao(): UserDao
     }
  7. 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>
  8. Add the string resources below into strings.xml.

     <string name="update">Update</string>
     <string name="partial_update">Partial Update</string>
  9. 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.

image3.png

The Update button will update update the entire User row when clicked.

Entity_Full_Update.gif

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.

Update_Entity_Name_with_Query.gif

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.

  1. 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
     )
  2. Back in UserDao.kt, add the Dao functions below. Each @Update annotation needs to pass in a value for the entity 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)
  3. 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.

Partial_UserName_Update.gif

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.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.