Android Kotlin

Exploring Android Room Persistence 2.2.0 Library Changes

Hello Everyone! On October 9, 2019, the Android team introduced a new version of Room persistence 2.2.0 and I’m very excited to talk about the new features introduced in this release. This was a really big release and it includes the following new features.

Note: I will not dive into details of the room since this post assumes you are already familiar with it. If not I recommend you to take a look at the official documentation before continuing with the rest of the post.

First, let’s add the newly updated room dependency into our app-level build.gradle file.

implementation 'androidx.room:room-runtime:2.2.0'
kapt 'androidx.room:room-compiler:2.2.0'

1. Working With Target Entities

The Dao classes annotation @Insert, @Update and @Delete now have a new property called targetEntity. The targetEntity field allows us to pass the different POJO class instances having the same columns of an entity class. Let’s see a quick example.

@Entity
data class User(
   @PrimaryKey val id : Long,
   val name : String,
   val email : String?
)

data class UserName(val id : Long, val name : String)
data class UserEmail(val id : Long, val email : String)

@Dao
interface UserDao {

   @Insert
   fun insertCompleterUser(user : User)

   @Insert(targetEntity = User::class)
   fun insertOnlyNameUser(userName : UserName)

   @Update(targetEntity = User::class)
   fun insertUserEmail(userEmail : UserEmail)
}

This allows us to leave out columns with default values without having to write the whole query by ourselves.

2. Return Coroutine Flow

Yes, you heard me right! It official now starting from 2.2.0 of room persistence library we can return Flow<T> for @Query methods. The returned Flow will re-emit a new set of values if the observing data inside the table are invalidated. Here’s how to use it.

@Dao
interface UserDao {
    @Query(value = "select * from users where age > :20")
    fun greaterThanTwentyAgeUsers() : Flow<List<User>>
}

class UserViewModel : ViewModel() {

     fun readAgeUsers() {
        viewModelScope.launch {
            userDao.greaterThanTwentyAgeUsers().collect { users ->
                    //  handle users
            }
        }
     }
}

The greaterThanTwentyAgeUsers method every time emit a new List<User> if the user’s table invalidated. You can check out the Issue Tracker for a more in-depth discussion.

3. Incremental Annotation Processing

This feature is a long-waited for the room annotation processor. Other famous libraries like Dagger, ButterKnife are already using incremental processor options which pretty much speeds up the build time. The room now supports increment ability via the processor option. You just need to add the following code inside the app-level build.gradle file.

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                    "room.incremental":"true"
                ]
            }
        }
    }
}

4. Expand Projections

This new expandProjecttions experimental compiler option is my favorite one and I hope they will stable it in the future release. Ever need to retrieve a List<User> while you only need to use some of its properties and all of the other are useless. First, let’s see a working example of what I’m talking about.

@Entity(tableName = "users")
data class User(@PrimaryKey val id : Long, val name : String, val email : String, /* and so many other properties to declare*/)

@Dao
interface UserDao {
     
      @Query(value = "select * from users")
      fun allUsers() : List<User>
}

fun main () {
     val dao = TODO("Retreive Dao")
     dao.allUsers().forEach {
         val name = it.name
         val email = it.email
         // don't need remaining user object properties
     }
}

You see in the above example I only need the name and email of a user object but my Dao only knows my Entity class that’s why I had to read the complete List<User>. Can we neglect the other properties?

Yes, this is where the new expandProjections feature shines. Now in order to enable this feature, we need to update our room compiler options like below.

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                    "room.incremental":"true",
                    "room.expandProjection":"true"
                ]
            }
        }
    }
}

Next, update the allUsers method with the required POJO class.

@Entity(tableName = "users")
data class User(@PrimaryKey val id : Long, val name : String, val email : String, /* and so many other properties to declare*/)

data class UserNameAndEmail(val name : String, val email : String)

@Dao
interface UserDao {
     
      @Query(value = "select * from users")
      fun allUsers() : List<UserNameAndEmail>
}

fun main () {
     val dao = TODO("Retreive Dao")
     dao.allUsers().forEach {
         val name = it.name
         val email = it.email
     }
}

The room persistence library will rewrite our query to only retrieve name and email from the users table SELECT name, email FROM users to satisfy the return type.

5. Schema Default Values

The @ColumnInfo annotation has a new property defaultValue which can be used to specify the default value of a column. Here’s how to use it.

@Entity(tableName = "users")
data class User(
    @PrimaryKey val id : Long,
    @ColumnInfo(name = "user_name", defaultValue = "temp") val name : String,
    @ColumnInfo(name = "user_email") val email : String
)

@Dao
interface UserDao {
    
     @Query(value = "INSERT INTO users (id,user_email) VALUES (:id,:email)")
     fun insertDefaultNameUser(id : Long, email : String)
}

Note: The defaultValue property is only valid with the @Query annotation method otherwise you need to provide the concrete value and the value specified in the annotation is never used.

Check out this link for handling default value when upgrading to Room 2.2.0.

6. Pre-packaged Database

Normally room databases are expected to be created during app runtime when needed. Now think about if you need to ship a pre-populated database as a part of your apk, or retrieve it from some kind of file that might serve your app as a starter data then this new feature is for you.

The two new methods are added inside the RoomDatabase.Builder class.

  1. createFromAssets: is for when the pre-populated database file is in the assets folder of the APK.
  2. createFromFile: is for when the file is in an arbitrary location.

For the detailed example of a pre-packaged room database with code check out this and this article.


That’s it I hope you enjoyed this list on room persistence 2.2.0 library changes. If you like my article don’t forget to hit the ♥️ button at the bottom of this article.

Also, to be notified about my new articles and stories subscribe to my website at home page.

Thank you for being here and keep reading…

2 Comments

  1. Hi ! I am facing issue in resolving “targetEntity”. I have added below dependencies into gradle file

    implementation ‘androidx.room:room-runtime:2.2.1’
    kapt ‘androidx.room:room-compiler:2.2.1’
    implementation “androidx.room:room-ktx:2.2.1”

    Please let me know if i am missing something.

    • ahsensaeed067 Reply

      Hey Radhe,
      Can you please show me your code Dao class?

Write A Comment