Migrating to Room From SQL

Rishabh Agarwal
4 min readJul 26, 2020

--

You must have heard of the phrase, SIZE MATTERS, and in the world of coding, this phrase has a special place in itself, and using jetpack components does this trick with full potential. So we spent this entire week on a single PR, where we migrated all the previous usage of SQL and Active Android to ROOM and we were able to get reduce 7500 lines to mere 1500 lines.

Problem

It is very easy to migrate to ROOM when all the tables are independent of each other and there are not many tables, but the problem we faced was:

  1. There were around 7 tables and each of them was extensively dependent on each other.
  2. The entire database was encrypted so we had to migrate it in one go
  3. We were using a very old library, Active Android, which helps to save data locally but since it wasn't updated since 2014, so it needed replacement.

Small steps to ultimate migration

There are 5 steps you need to follow to perfectly implement ROOM and get away with a hell lot of boilerplate code. One cannot manage complete migration in a single PR so we break it into smaller ones where we first create Entity classes, then create DAO classes with tests, and only after that, we move on to migration.

1. Creating Entity classes:

First, create an Entity class that has all the fields that are present in your existing database table. This class is annotated with @Entity .You can use @Ignore annotation if you want to add some variables that you need to use but don't want ROOM to persists. Your entity class must look like this:

@Entity(tableName = “concepts”)
class ConceptEntity : Resource() {
@ColumnInfo(name = “name”)
var name: String? = null
@Ignore
var name: String? = null
}

2. Creating a DAO interface:

Now, you need to create DAO interface class that contains all the queries which you might be using in the previous classes to add, remove, or retrieve tables. Previously, you had to write all the code to send a query, create a cursor, and then retrieve the column data from the cursor, but now ROOM takes care of all of that itself.

With the latest room update, we can use Room and RxJava2 together using Maybe, Single and Flowable which I will cover in a separate article. They help to make all the calls asynchronous in nature.

@Dao
public interface ConceptRoomDAO
{
@Insert
void addConcept(ConceptEntity conceptEntity);
@Update
void updateConcept(ConceptEntity conceptEntity);
@Query("SELECT * FROM concepts WHERE uuid = :uuid")
Single<List<ConceptEntity>> findConceptsByUUID(String uuid);
}

3. Creating an AppDatabase class:

The third step is to create an AppDatabase class. We need to define an abstract class that extends RoomDatabase. This class is annotated with @Database, which has the entities contained in the database, and the DAOs which access them directly. This class brings all the pieces of ROOM together.

@Database(entities = {ConceptEntity.class}, version = 1) 
public abstract class AppDatabase extends RoomDatabase {

private static volatile AppDatabase INSTANCE;
public abstract ConceptRoomDap conceptRoomDAO();

public static AppDatabase getDatabase(final Context context) {
INSTANCE = Room.databaseBuilder(context,AppDatabase.class,name)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
return INSTANCE;
}

4. Creating Unit tests:

The most important step before you proceed to migration is creating Unit Tests for ROOM DAO classes. This is very crucial because this helps you to see if all the queries are working properly or not. You can pass null values to the room to check if there are any null pointer exceptions or not. Only after all the tests pass, you should start the migration task.

To test asynchronous calls like Single, Maybe, Flowable, you should make sure that tests are also asynchronous in nature. This is my way of the testing room calls:

@Test    
public void findConceptsByUUID_shouldGetCorrectConceptByUUID() {
conceptRoomDAO().addConcept(expectedConceptEntity1);
conceptRoomDAO().addConcept(expectedConceptEntity2);

conceptRoomDAO().findConceptsByUID(expectedConceptEntity1.getUuid())
.test()
.assertValue(entities -> {
ConceptEntity actualEntity = entities.get(0);
// Assert values to test
});
}

5. Final Migration

With all the DAO classes ready, you can replace all the instances of previous model classes and database calls with the new Entity classes and DAO calls and all your code to get data from cursor will be reduced along with all other unnecessary boilerplate code…

Remove any unused classes or lines of code that are now replaced by Room functionality. In my project, we just had to delete the DBOpenHelper class, that was extending the SQLiteOpenHelper class, Tables, other model classes.

The amount of boilerplate, code causing NPE, and RTE decreased, the queries are now checked at compile time and everything is testable. In 5 steps, I was able to migrate our existing app to Room which was way too complex.

For any help, you can look at the PR that I created which helped us migrate the entire codebase to room at https://issues.openmrs.org/browse/AC-470

--

--

Rishabh Agarwal

Android developer | Technical writer | Open Source Contributor