Using Room with LiveData and other third-party libraries

Note: This article is part of the advanced Room series which covers all the details about the Room persistence library. You can read all the articles here:

In this blog, we will learn how to use Room with LiveData and other third-party libraries.

Room with LiveData

In the previous articles, we have discussed how we can use Room persistence library in our Android applications and how Room internally works. Also, we discussed about the different kind of operations we can do easily with the help of Room. Using LiveData with Room is also one of them.

Room supports the integration of LiveData very easily. You just need to return LiveData from your DAO methods and Room takes care of everything else for you.

Observable queries with LiveData

When performing queries, you’ll often want your app’s UI to update automatically when the data changes. To achieve this, use a return value of type LiveData in your query method description.

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllLiveData(): LiveData<List<User>>
}

Room generates all necessary code to update the LiveData when the database is updated. The generated code looks like this:

@Override
public LiveData<List<User>> getAllLiveData() {
  final String _sql = "SELECT * FROM users";
  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
  return __db.getInvalidationTracker().createLiveData(new String[]{"users"}, false, new Callable<List<User>>() {
    @Override
    public List<User> call() throws Exception {
      final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
      try {
        final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
        final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
        final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "last_name");
        final List<User> _result = new ArrayList<User>(_cursor.getCount());
        while(_cursor.moveToNext()) {
          final User _item;
          final int _tmpUid;
          _tmpUid = _cursor.getInt(_cursorIndexOfUid);
          final String _tmpFirstName;
          _tmpFirstName = _cursor.getString(_cursorIndexOfFirstName);
          final String _tmpLastName;
          _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
          _item = new User(_tmpUid,_tmpFirstName,_tmpLastName);
          _result.add(_item);
        }
        return _result;
      } finally {
        _cursor.close();
      }
    }

    @Override
    protected void finalize() {
      _statement.release();
    }
  });
}

In the above code, __db.getInvalidationTracker().createLiveData() takes tableNames array, inTransaction boolean flag and computeFunction callable.

  • tableNames is used for by RoomTrackingLiveData to observe for changes.
  • inTransaction indicates whether the query has to be performed as a transaction or not.
  • computeFunction is a callable which is called whenever there are any changes in observed tables.

We can also see that the call() method of computeFunction does the actual execution of our query. It’s invoked when an observer starts observing the LiveData or whenever any changes happen in observed tables.

Room with RxJava

In the previous articles, we have discussed about using Room persistence library in our Android applications. We also discussed about the various operations we can perform using Room DAOs. Working with RxJava observables also becomes very easy using Room.

For returning RxJava observables from your DAO methods, we need to add the following dependency in our build.gradle first:

dependencies {
  def room_version = "2.2.5"

  implementation "androidx.room:room-runtime:$room_version"
  kapt "androidx.room:room-compiler:$room_version"

  // RxJava support for Room
  implementation "androidx.room:room-rxjava2:$room_version"
}

Reactive queries with RxJava

Room provides the following support for return values of RxJava2 types:

@Dao
interface UserDao {
    @Query("SELECT * from users where uid = :id LIMIT 1")
    fun loadUserById(id: Int): Flowable<User>

    @Insert
    fun insertUsers(vararg users: User): Completable

    @Delete
    fun deleteAllUsers(users: List<User>): Single<Int>
}

Implementation of these methods look like the following:

@Override
public Completable insertLargeNumberOfUsers(final User... users) {
  return Completable.fromCallable(new Callable<Void>() {
    @Override
    public Void call() throws Exception {
      __db.beginTransaction();
      try {
        __insertionAdapterOfUser.insert(users);
        __db.setTransactionSuccessful();
        return null;
      } finally {
        __db.endTransaction();
      }
    }
  });
}@Override
public Single<Integer> deleteAllUsers(final List<User> users) {
  return Single.fromCallable(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
      int _total = 0;
      __db.beginTransaction();
      try {
        _total +=__deletionAdapterOfUser.handleMultiple(users);
        __db.setTransactionSuccessful();
        return _total;
      } finally {
        __db.endTransaction();
      }
    }
  });
}@Override
public Flowable<User> loadUserById(final int id) {
  final String _sql = "SELECT * from users where uid = ? LIMIT 1";
  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
  int _argIndex = 1;
  _statement.bindLong(_argIndex, id);
  return RxRoom.createFlowable(__db, false, new String[]{"users"}, new Callable<User>() {
    @Override
    public User call() throws Exception {
      //Implementation 
    }

    @Override
    protected void finalize() {
      _statement.release();
    }
  });
}

From the autogenerated code, we can see that Room is using the fromCallable() operator to create Completable and Single. In RxRoom.createFlowable , Room uses Flowable.create() to create Flowable.

Code inside call() method of callable is similar to the non-reactive implementation.

Room with Kotlin Coroutines

In the previous articles, we have discussed about the Room persistence library and the advantages of using Room. One of the advantages of using Room is how easily we can integrate it with other libraries like Kotlin Coroutines. So let’s discuss how can we achieve it with the help of Room.

First of all, we need to add the following dependency for Kotlin coroutines support:

dependencies {
  def room_version = "2.2.5"

  implementation "androidx.room:room-runtime:$room_version"
  kapt "androidx.room:room-compiler:$room_version"  // Kotlin Extensions and Coroutines support for Room
  implementation "androidx.room:room-ktx:$room_version"
}

Write async methods with Kotlin coroutines

You can add the suspend Kotlin keyword to your DAO methods to make them asynchronous using Kotlin coroutines functionality. This ensures that they cannot be executed on the main thread.

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUsers(vararg users: User)    @Update
    suspend fun updateUsers(vararg users: User)    @Delete
    suspend fun deleteUsers(vararg users: User)    @Query("SELECT * FROM users")
    suspend fun loadAllUsers(): Array<User>
}

Room generates the implementation of all the above methods. Implementation of suspend functions is very similar to non-suspend ones.

@Override
public Object insertUsers(final User[] users, final Continuation<? super Unit> p1) {
  return CoroutinesRoom.execute(__db, true, new Callable<Unit>() {
    @Override
    public Unit call() throws Exception {
      __db.beginTransaction();
      try {
        __insertionAdapterOfUser.insert(users);
        __db.setTransactionSuccessful();
        return Unit.INSTANCE;
      } finally {
        __db.endTransaction();
      }
    }
  }, p1);
}

Here, CoroutinesRoom.execute executes the passed callable asynchronously. Implementation of CoroutinesRoom.execute is not available.

Non suspend function looks like the following:

@Override
public void insertUsers(final User... users) {
  __db.assertNotSuspendingTransaction();
  __db.beginTransaction();
  try {
    __insertionAdapterOfUser.insert(users);
    __db.setTransactionSuccessful();
  } finally {
    __db.endTransaction();
  }
}

The only difference between both implementations is that in case of suspend functions, CoroutinesRoom.execute is responsible for suspending the execution flow.

This is all about the use of LiveData, RxJava, and Kotlin Coroutines with Room. Hope you enjoyed this blog.

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

Thank You!!!