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:
- Introduction to Room Persistent Library in Android [You are here]
- Data Access Objects - DAO in Room
- Entity Relationship in Room
- How does Room work internally?
- Room Database Migrations
- Using Room with LiveData and other third-party libraries
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)
file:
build.gradle
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 .
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
should satisfy the following conditions:
@Database
-
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
Room.databaseBuilder()
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 aobject. Each
RoomDatabase
instance is fairly expensive, and you rarely need access to multiple instances within a single process.
RoomDatabase
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
autoGenerate
@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
@Entity
@ ColumnInfo
@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 theunique
@Index
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
annotation to the
@TypeConverters
class so that Room can use the converter that you've defined for each
entity
and
DAO
in that
RoomDatabase
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!!!