Android Room persistence library — Using the delegate pattern to update the UI after a database operation

If you are using the room persistence library to handle Sqlite database operations on your android app, you should probably know that the library forces the developer to do all its db calls inside a background thread. So, if you are using the DAO pattern and wants to keep database logic, including the asynchronous executions encapsulated in your DAO classes, you will need to design them in a way that they can notify the calling activity after the background process finishes. After that, UI updates can take place on the main thread.

Now, I will show you how I could achieve this by making using of inner class and delegate pattern. For this example, we will need to write 4 classes and 2 interfaces and I will try to explain the code of the most important portions. Explaining basic Room concepts is not the objective of this article and we are going to focus on the main theme here. Before we can go ahead, I want to make clear that I’m not trying to reinvent the wheel. Some concepts used in this example are well known and I’m just put things together to do what we need. So, let’s go.

First, let’s create a simple class for the model we are using in this example.

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
@Entity
public class Person {
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

Now, we are gonna need a Dao interface to define the DB basic operations.

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import java.util.List;
@Dao
public interface PersonDao {
@Insert
void
insert(Person person);
@Query("select * from Person order by name")
List<Person> getAllPersons();
}

After that, let’s write the database class.

import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.Database;
import android.content.Context;
@Database(entities = {Person.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static volatile AppDatabase INSTANCE;
private static final String DATABASE_NAME = "PersonDB";
public abstract PersonDao personDao();
public static synchronized AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
//Create database object
INSTANCE = Room.databaseBuilder(context, AppDatabase.class, DATABASE_NAME).build();
}
return INSTANCE;
}
}

As you can see, we are using a singleton pattern for the Room database, by doing this, we can have a single database instance for the entire application.

Now, let’s proceed with the interface which will be used to implement the delegate.

public interface AfterDBOperationListener {
public void afterDBOperation(int result);
}

This interface has one method signature that will be used to do the callback when the background operation finishes.

The next step consists in writing a repository class that will handle UI persistence calls. This design type is useful to let the DAO do only database operations besides providing a way to to handle multiple datasources in the app. To clarify this concept, imagine that your application needs to do a persistence operation on a local database and on a back end API. The repository class would be responsible for managing the calls to these two layers. That said, we can continue with the repository coding.

import android.content.Context;
import android.os.AsyncTask;
public class PersonRepository {
private final PersonDao personDao;
private AfterDBOperationListener delegate;
public PersonRepository(Context context) {
AppDatabase db = AppDatabase.getInstance(context);
personDao = db.personDao();
}
public void insert(Person person) {
new InsertAsyncTask(personDao, delegate).execute(person);
}
public void setDelegate(AfterDBOperationListener delegate) {
this.delegate = delegate;
}
private static class InsertAsyncTask extends AsyncTask<Person, Void, Integer> {
        private PersonDao asyncPersonDao;
private WeakReference<AfterDBOperationListener> asyncDelegate;
InsertAsyncTask(PersonDao personDao, AfterDBOperationListener afterDBOperationListener) {
asyncPersonDao = personDao;
asyncDelegate = new WeakReference<>(afterDBOperationListener);
}
@Override
protected Integer doInBackground(Person... persons) {
asyncPersonDao.insert(persons[0]);
return 1;
}
@Override
protected void onPostExecute(Integer result) {
final AfterDBOperationListener delegate = asyncDelegate.get();
if (delegate != null)
delegate.afterDBOperation(result);
}
}
}

Let’s try to understand what we did here by explaining parts of the code.

private final PersonDao personDao;
private AfterDBOperationListener delegate;
public PersonRepository(Context context) {
AppDatabase db = AppDatabase.getInstance(context);
personDao = db.personDao();
}
public void insert(Person person) {
new InsertAsyncTask(personDao, delegate).execute(person);
}
public void setDelegate(AfterDBOperationListener delegate) {
this.delegate = delegate;
}

In the snippet above, we have two member variables, one for the DAO instance (personDAO) and one for the delegate instance (delegate). After that, we have a constructor that receives a Context and instantiates a database class, getting the DAO that will be needed from it to store in the personDAO variable. Then, we have the insert method which receives a model object (person) and instantiates an InsertAsyncTask class passing the DAO and the delegate and after that calls the execute method passing a model(person) as a parameter. Finishing the snippet above, there’s a setter for the delegate member variable.

From now on, I will try to explain the last part of the repository class, that consists on an inner class that extends the good and old android AsyncTask class used to perform the background database operation required by the Room library.

private static class InsertAsyncTask extends AsyncTask<Person, Void, Integer> {
private PersonDao asyncPersonDao;
private AfterDBOperationListener asyncDelegate;
InsertAsyncTask(PersonDao personDao, AfterDBOperationListener afterDBOperationListener) {
asyncPersonDao = personDao;
asyncDelegate = afterDBOperationListener;
}
@Override
protected Integer doInBackground(Person... persons) {
asyncPersonDao.insert(persons[0]);
return 1;
}
@Override
protected void onPostExecute(Integer result) {
if (asyncDelegate != null)
asyncDelegate.afterDBOperation(result);
}
}

Basically, what we have here is an inner class that receives the DAO and the delegate instances from the containing class to do the background task and call the method whose implementation was delegated to another class. In this case, asyncDelegate.afterDBOperation(result) located inside the onPostExecute method. We will not discuss here the basics of the AsyncTask class. This article assumes that you already know how it works.

Finally, let’s write the Activity class, where all the process begin.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
public class PersonActivity extends AppCompatActivity implements AfterDBOperationListener {
private PersonRepository personRepository;
@Override
protected
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person);
personRepository = new PersonRepository(getApplicationContext());
personRepository.setDelegate(this);
Person person = new Person();
person.setName("Someone");
personRepository.insert(person);
}
@Override
public
void afterDBOperation (int result) {
if (result == 1) {
Toast.makeText(this, "Person successfully saved!", Toast.LENGTH_SHORT).show();
}
}
}

The activity above is very simple and the only thing it does is inserting a person record in the database through the repository instance. As we can see, the person data is hardcoded, there is no form for the user to input data. That’s good enough for this example. All we need to achieve here is to update the user interface using the main thread after the execution of a secondary thread. That’s done by implementing the afterDBOperation method from the DBOperationListener interface and setting the delegate field of the repository object with the activity instance. That said, the most important code parts here are the next ones:

personRepository = new PersonRepository(getApplicationContext());
personRepository.setDelegate(this);

Setting the delegate field of the repository like the code snippet above.

@Override
public
void afterDBOperation (int result) {
if (result == 1) {
Toast.makeText(this, "Person successfully saved!", Toast.LENGTH_SHORT).show();
}
}

Implementing the interface method. That way, we can delegate the UI update to the Activity after a database operation like we wanted.

Thus, we reached the end of this article. I hope that I was clear and you could make some use of this example. Thanks for reading.