Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Because Room takes care of several concerns for you, e.g. caching, networking, Android highly recommend using Room instead of SQLite. If you are coming from iOS, the good news is that Room is very similar to the core data in iOS development.
The task
Field name | Type |
---|---|
uid | Integer |
name | String |
photo | Bitmap |
dob | LocalDate |
We will create a user data model, similar to the one from Android documentation website. Nevertheless, we will add more complicated data to the model as well as some queries.
The user model will look like this.
Getting started
To use Room in your app, add the following dependencies to your app’s build.gradle
file:KOTLINJAVA
First you will need to add kapt
to your project build.gradle
:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
Secondly, you will add the room’s dependencies.
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:
room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:
room_version"
}
Sync your project before creating the data model.

Implement data model
With Room, we use all annotation, e.g. @Entity
to denote the class as a data entity (table), @PrimaryKey
for the primary key. The Room framework will handle the table creation for you.
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "name") val name: String?,
@ColumnInfo(name = "dob") val dob: LocalDateTime?,
@ColumnInfo(name = "photo") val photo: Bitmap? = null
)
data access objects (dao)
To access the table created above, we need to declare a DAO
class. The following code provides the standard methods in the database, i.e. insert, delete, select. Similarly, we use @Query
to define the custom query that we want to assign to the method, use @Delete
to create standard delete user.
@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>
@Query("SELECT * FROM user WHERE name LIKE :name LIMIT 1")
fun findByName(name: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
Database
The database to store these tables can be defined as follow
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Address the uncommon data types
Now if you try to compile your app, you will have the following error
error: Cannot figure out how to save this field into database.
Execution failed for task ':app:kaptDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution
> java.lang.reflect.InvocationTargetException (no error message)
It is because there are data types, e.g. Bitmap
and LocalDate
that Room does not know how to store it automatically. To solve this, we need to provide relevant methods for retrieve and save these data. We use @TypeConverter
for these methods.
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): LocalDateTime? {
return value?.let {
Instant.ofEpochMilli(it)
.atZone(ZoneId.of("UTC")).toLocalDateTime()
}
}
@TypeConverter
fun dateToTimestamp(date: LocalDateTime?): Long? {
return date?.atOffset(ZoneOffset.UTC)?.toInstant()?.toEpochMilli()
}
@TypeConverter
fun fromByteArray(bitmapdata: ByteArray?): Bitmap? {
return bitmapdata?.let {
BitmapFactory.decodeByteArray(bitmapdata, 0, bitmapdata.size)
}
}
@TypeConverter
fun dateToByteArray(bmp: Bitmap?): ByteArray? {
val stream = ByteArrayOutputStream()
bmp?.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray: ByteArray = stream.toByteArray()
return byteArray
}
}
Now we can update the Appdatabase
with the annotataion @TypeConverters
as follow
@Database(entities = arrayOf(User::class), version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
It should compile normally.
Define relations
If you have another table, e.g. Library and you want to set user-library relations
@Entity
data class Library(
@PrimaryKey val libraryId: Long,
val userOwnerId: Long
)
To do this, create a new data class where each instance holds an instance of the parent entity and the corresponding instance of the child entity. Add the @Relation
annotation to the instance of the child entity, with parentColumn
set to the name of the primary key column of the parent entity and entityColumn
set to the name of the column of the child entity that references the parent entity’s primary key.
data class UserAndLibrary(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
val library: Library
)
Tips for Some other problems
- If your project failed to build, it is likely that you have issue with
Dao
class. Check it thoroughly. - If you have problem with schema, you can add to defaultConfig of
build.gradle
defaultConfig {
//...
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
//...
}
USEFUL LINKS
- Source code on Github
- Mobile game tutorials
- Subscribe to Petamind channel