Introduction to Room Persistent Library in Android

Introduction to Room Persistent Library in Android

Welcome to the Advanced Room series which covers all the details about Room Persistent Library. In this series, we will start with the basics of Room, then we will learn how to use it. We will learn the components of Room in detail and we will also learn how to use Room with LiveData and other third-party libraries like RxJava and Kotlin coroutines. Here is the list of the blogs of this series:

So, let's start with the Introduction to Room Persistent Library.

This part covers the basics of Room persistence library. After reading this, you can start using Room in your android applications.

Room is an Android persistence library which is part of Google’s Android Jetpack project. According to the documentation, Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.

Apps that handle significant amounts of structured data can benefit greatly from persisting that data locally. The most common use case is to cache relevant pieces of data. That way, when the device cannot access the network, the user can still browse that content while they are offline. Any user-initiated content changes are then synced to the server after the device is back online.

Adding Room in your project

Add following in your module’s(app) build.gradle file:

dependencies {
  implementation "androidx.room:room-runtime:2.2.5"
  kapt "androidx.room:room-compiler:2.2.5"
}

Advantages of using Room

There are multiple advantages of using Room as compared to other alternate solutions like SQLiteOpenHelper:

  • Compile-time verification of queries.
  • Reduces boilerplate code.
  • Easy to understand and use.
  • Easy integration with RxJava, LiveData and Kotlin Coroutines.

Components of Room

There are 3 major components in Room:

  • Database: Contains the database holder and serves as the main access point for the underlying connection to your app’s persisted, relational data.
  • Entity: Represents a table within the database.
  • DAO: Contains the methods used for accessing the database.

Your application uses the Room database to get the data access objects, or DAOs, associated with your database. The app then uses each DAO to get entities from the database and save any changes to those entities back to the database. Finally, the app uses an entity to get and set values that correspond to table columns within the database.

Introduction to Room Persistent Library in Android

Database

As mentioned earlier, it contains the database holder and serves as the main access point for the underlying connection to your app’s persisted, relational data. The class that’s annotated with @Database should satisfy the following conditions:

  • Be an abstract class that extends RoomDatabase.
  • Include the list of entities associated with the database within the annotation.
  • Contain an abstract method that has 0 arguments and returns the class that is annotated with @Dao
  • At runtime, you can acquire an instance of Database by calling Room.databaseBuilder() or Room.inMemoryDatabaseBuilder()
@Database(entities = arrayOf(User::class), version = 1)
abstract class UserDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}

To get an instance of the database, you can use the following method:

val db = Room.databaseBuilder(
    applicationContext,
    UserDatabase::class.java, "users-db"
    ).build()
Note: If your app runs in a single process, you should follow the singleton design pattern when instantiating a RoomDatabase object. Each RoomDatabase instance is fairly expensive, and you rarely need access to multiple instances within a single process.

Entity

An Entity represents a table within a database. This class is annotated with @Entity annotation. Data members in this class represent the columns within a table.

@Entity
data class User(
  @PrimaryKey val uid: Int,
  @ColumnInfo(name = "first_name") val firstName: String?,
  @ColumnInfo(name = "last_name") val lastName: String?
)
  • All the fields in an entity must either be public or have getter & setter methods.
  • Entity class should have an empty constructor (if all fields are accessible) or a parameterized constructor which takes all the fields. Room can also use partial constructors.
  • Each entity class must have atleast one primary key. You can use either @PrimaryKey annotation to define single field primary key or primaryKeys attribute of @Entity annotation for multiple fields. You can also use autoGenerate property of @PrimaryKey annotation to automatically assign primary keys.
@Entity(primaryKeys = arrayOf("firstName", "lastName"))
  • By default, Room uses the class name as the database table name. If you want the table to have a different name, set the tableName property of the @Entity annotation. Similarly, you can use the name property of the @ColumnInfo annotation for defining the name of columns.
@Entity(tableName = "users")
  • If you don’t want to persist any field, you can annotate them using @Ignore.
@Ignore val picture: Bitmap?
  • You can use the indices property of @Entity annotation to add indices to an entity. Also, you can create unique indices by setting the unique property of an @Index annotation to true.
@Entity(indices = arrayOf(Index(value = ["last_name", "address"])))@Entity(indices = arrayOf(Index(value = ["first_name", "last_name"],
        unique = true)))

Data Access Object (DAO)

DAOs provide an API for accessing the database. This is an interface which is annotated with @Dao annotation. All the methods in this interface are used for getting data from the database or making changes to the database. These methods are annotated with annotations like @Query, @Insert, @Delete.

@Dao
interface UserDao {
  @Query("SELECT * FROM user")
  fun getAll(): List<User>
  
  @Query("SELECT * FROM user WHERE uid IN (:userIds)")
  fun loadAllByIds(userIds: IntArray): List<User>
  
  @Insert
  fun insertAll(vararg users: User)
  
  @Delete
  fun delete(user: User)
}
Note: All queries using UserDao are made on the caller thread. So you should take care that no method is invoked from the UI(main) thread.

Type Converters

Sometimes, you might need to persist a custom data type in a single database column. You can use type converters for these type of use cases.

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
  return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
  return date?.time?.toLong()
  }
}

Next, you have to add the @TypeConverters annotation to the RoomDatabase class so that Room can use the converter that you've defined for each entity and DAO in that RoomDatabase.

@Database(entities = arrayOf(User::class), version = 1)
@TypeConverters(Converters::class)
abstract class UserDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

This is all about the basics of Room. Hope you enjoyed this blog. In the next blog, we will learn about Data Access Objects - DAO in Room.

You can also connect with me on LinkedIn, Twitter, Facebook and Github.

Thank You!!!