Setting Android Room in real project

Thibault de Lambilly
AndroidPub
Published in
5 min readNov 3, 2017

--

Photo by Kipras Štreimikis on Unsplash

Already many articles on Room and other Google components, but many of them are also just a copy of examples from the official documentation. Setting Room up in an actual project may be a little more tricky. Here, how I did it in my project.

Context

So, I have developed an Android app in Kotlin that allows practising questions on a subject (pilot knowledge for me). It is above all, also my playground to test libraries and dev practices. I have done an offline version thanks to Realm, but I wanted to test Room integration.

Setup the environment

Nothing hard here, everything explain enough on the official page

Create my domain objects

I have a Subject object that has a list of Topic object

To define your entity just add @Entity annotation, possibly with the tableName information. You need a @PrimaryKey , obviously mandatory

To be easily synchronise with my online database, I handle my self the id value, otherwise you would add autoGenerated=true param to @PrimaryKey

import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Ignore
import android.arch.persistence.room.PrimaryKey

@Entity(tableName = "subject")
class Subject() {

@PrimaryKey
@ColumnInfo(name = "idWeb")
var idWeb: Long = -1

var name: String = ""

@Ignore
constructor(idWeb:Long, name: String): this() {
this.idWeb = idWeb
this.name = name
}
}

The important thing to know is that Room must have only one constructor to deal with. Thus use @Ignore on other constructor(s) to tell Room not to bother.

Here it is just an example, because in fact I use a data object declaration for that Object. But it will important to understand it for the embedded object later.

Second object:
My Topic object has a Foreign Key to the Subject object. As I have a Foreign key, I must have an Index as well (otherwise at compilation time, Room will shout at you)

import android.arch.persistence.room.*@Entity(
tableName = "topic",
indices= arrayOf(Index(value = "subject_id", name = "idx")),
foreignKeys = arrayOf(ForeignKey(
entity = Subject::class,
parentColumns = arrayOf("idWeb"),
childColumns = arrayOf("subject_id"))
)
)
class Topic(
@PrimaryKey
@ColumnInfo(name = "idWeb")
val idWeb: Long,
@ColumnInfo(name = "subject_id")
val subjectId: Long,
var name: String = ""
)

Handle the OneToMany relation

As said in the documentation:

Room disallows object references between entity classes. Instead, you must explicitly request the data that your app needs.

Thus how to handle it? There are two ways:

  • 1 — Either you put your list inside your domain object with an Ignore on the list, then gather the data yourself and fill the list, such as:
class Subject() {
...
@Ignore
var topics: List<Topic>? = null
}
  • 2 — Or you use a POJO that has an @Embedded object and @Relation with your list.

I prefer that second approach as you get your object with your OneToMany relation directly from the database.

class SubjectView(
@Embedded
var subject: Subject = Subject(-1, -1, ""),

@Relation(
parentColumn = "idWeb",
entityColumn = "subject_id",
entity = Topic::class)
var topics: List<Topic> = listOf()
)

In that use, the @Embedded means that you can query the Subject fields directly in your Dao query and fill the object with the return data. Otherwise, it can be used in an entity object to flatten Pojo that you would reuse in your project (eg: an Address object).

With the @Relation Room will automatically fill the list with the related data. Cool right ;-)

Watch out how the object is declared. All params are initialized.

It took me a while and a Stackoverflow question to know why it is important (otherwise it is not compiling at all). It’s the Room mandatory single constructor that force to do that. In fact it’s because I may have no data for my List<Topic> that Kotlin would generate several constructors that bother Room. Much more here:

Create your Dao object

Dao object are simple, almost just @Dao annotation and copy/paste always the same @Query and your are good to go

// SubjectDao.ktimport android.arch.lifecycle.LiveData
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Query
import biz.eventually.atpl.data.db.Subject
import biz.eventually.atpl.data.dto.SubjectView

@Dao
abstract class SubjectDao : BaseDao<Subject>() {

@Query("SELECT * FROM subject")
abstract fun getAll(): LiveData<List<Subject>>

@Query("SELECT * FROM subject WHERE source_id = :sourceId")
abstract fun findBySourceId(sourceId: Long): LiveData<List<SubjectView>>

@Query("SELECT idWeb FROM subject")
abstract fun getIds(): List<Long>

@Query("SELECT * FROM subject WHERE idWeb = :idWeb")
abstract fun findById(idWeb: Long): Subject?
}
// BaseDao.kt
@Dao
abstract class BaseDao<in T> {

@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(type: T): Long

@Update
abstract fun update(type: T)

@Delete
abstract fun delete(type: T)
}

And on my findBySourceId is returning my SourceView while I am just querying the Subject Dao

@Query("SELECT * FROM subject WHERE source_id = :sourceId")

No need to specify Subject object variables, unless don’t want all of them to be filled.

All access to the SqLite database through Room must be outside the Main Thread. Using LiveData interface magically allows you to query directly your Daos without bother with Runnable or RxJava implementation (such as Flowable ) which is not my strong suit, I must confess. And using LiveData is good with ViewModel later on.

To use the others queries, such as Insert, Update, Delete , I use an Anko shortcut to request it asynchronously, that is too me, so cleaner that declare a new Runnable().

import org.jetbrains.anko.doAsyncdoAsync {
...
dao.insert(myObject)
...
}

Create the database

You need 2 things to put the database alive:

  • Declare an abstract class with @Database annotation and extending RoomDatabase. Inside declare your object dao’s
  • Initiate that database class to use it.

In short, as mention on the documentation:

// File: AppDatabase.java
@Database(entities = {User.java})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
AppDatabase db = Room.databaseBuilder(
getApplicationContext(),
AppDatabase.class,
"database-name"
).build();

I’m using Dagger for Dependency Injections, thus to me with Koltin, it looks like more like this:

// AppDatabaseModule.kt
@Database(
entities = arrayOf(Subject::class, Topic::class),
version = 1
)
abstract class AppDatabase : RoomDatabase() {

abstract fun sourceDao() : SourceDao
abstract fun subjectDao() : SubjectDao
abstract fun topicDao() : TopicDao

}
// DatabaseModule.kt@Singleton
@Provides
fun provideDatabase(context: Context) : AppDatabase {

return Room.databaseBuilder(
context,
AppDatabase::class.java,
"mydb.db"
).fallbackToDestructiveMigration()
.build()
}

Those 2 importantes things are : the list of entities object and the database version.

It is “easy” to remember to change the version, but I continue to forget to add entity on the list while creating a new one.

The fallbackToDestructiveMigration says that if there is no migration class provided with an incrementation of the db version, it will drop any table and recreate the database. It allows to avoid the crash while incrementing the database version without migration strategy (otherwise than be ok to loose all the data).

Use your Dao

first I just provide my dao’s by DI to use it everywhere.

@Singleton
@Provides
fun provideSourceDao(db: AppDatabase) : SourceDao {
return db.sourceDao()
}

Then I can inject it in a repository for instance. And that’s it!

--

--

Thibault de Lambilly
AndroidPub

Mobile enthusiast, Kotlin lover | full stack by pleasure