Clean, Easy & New- How To Architect Your App: Part 2 — Persistency

Britt Barak
ProAndroidDev
Published in
6 min readJun 8, 2017

--

Implementing Repository class with Room.

Previous post suggested an idea for architecting Android app, using the new Android Architecture components.

We talked about the architecture layers and separation between them:

I presented a jelly bean recipes app that will be used for demonstration. If you haven’t yet, take a couple of minutes to read it, so that everything can be clearer.

Last time we mainly discussed the Presentation layer. And now..

Time to save the bean!

After filling in the flavor name and color, we’ll click on the “SAVE” button, to save the jelly bean.

How should we do that?

The button belongs to the Presentation Layer. The jelly bean once saved, becomes data, which belongs, obviously, to the Data Layer. Since the layers are separated, the button doesn’t know where the jelly bean data will be saved, how will it be represented when stored, or even whether or not will it be saved.

The Data Layer is composed of 2 main object types:

  1. Data model objects
  2. Repository objects

1. Data model objects

A data model is a POJO which represent the data in the most “objective” way- as it is represented by the data structure : by the server, local databases etc... As discussed before, view model objects serve the UI for presentation purposes. Likewise, the data model objects serve the data structures, for storage purposes.

For example:

We already saw the JellyBeanViewModel:

public class JellyBeanViewModel extends ViewModel {
String flavor;
int r;
int g;
int b;

The JellyBean data model will be:

public class JellyBean {
public String flavor;
public String color;

As for our app it is more convenient to use r,g,b integers to represent color, the server side, for its own reasons decided to represent color by a string.

Our app should handle the current situation, as well as to support future changes easily: the server might change the color representation; or we might use a different data source that represents jelly bean objects in a whole different way.

To support this, a proper separation between the view and data models is good starting point.

2. Repository objects

Each Repository is in charge of getting and setting a single type of data (e.g jelly bean, recipe, user….), and encapsulate the logic of doing it. That way if we want to change the way we get or save the data, only the Repository knows about it.

For example:

Let’s say I have an appClient object that handles the requests to my server. When I request for a Jelly Bean it would look something like that:

public void getJellyBeans(Callback callback) {
appClient.getJellyBeans(callback);
}

Notice that I use a Callback parameter. Currently its implementation doesn’t really matter. The point is, that since we don’t know where the data will come from, we have to get it asynchronously, in order to eliminate the option of blocking the UI thread. Therefore, some kind of a callback would be mandatory.

If I decide to add some kind of a cache, to avoid an “expensive” network request- I can do it quite easily:

public void getJellyBeans(Callback callback) {
if (cache.hasJellyBeans()) {
callback.success(cache.getJellyBeans());
}
else {
appClient.getJellyBeans(callback);
}
}

If later on, I decide that the in-memory cache isn’t enough, and I want to be able to persist the data between app sessions- I can very easily add a local database of some sort:

public void getJellyBeans(Callback callback) {
if (cache.hasJellyBeans()) {
callback.success(cache.getJellyBeans());
} else if(database.hasJellyBeans()) {
callback.success(database.getJellyBeans());
}
else {
appClient.getJellyBeans(callback);
}
}

So the Repositories give us a great abstraction layer that encapsulate the logic of getting (or setting) the data, and create a separation between the Data Layer and the Presentation layer: the UI doesn’t care where does the data come from. Also, it is really easy to test — only clear logic.

Domain Layer In Action

And yet, how do we connect between the Presentation and the Data Layers?

The Layer between these two, is the Domain Layer, which is combined of Interactor objects. Each Interactor represents some use case, meaning something that we want to do by interacting UI and data. For example: get all the jelly beans and present them, or here: take the jelly bean that is presented on the screen and save it.

In other words, an Interactor knows how to take a View Model that the UI can use and transform it into a Data Model- which the Data Layer can use, and vice versa.

I created a SaveJellyBean object, which is my use case, which is a sort of Interactor.

It takes the JellyBeanViewModel (as used by the UI) → transform it into JellyBean (which is the Data Model) → asks the JellyBeanRepo to save it.

public class SaveJellyBean extends UseCase {
JellyBeanRepo repo;

public SaveJellyBean(Context context, LifecycleOwner lifeCycle){
repo = JellyBeanRepo.getInstance(context, lifeCycle);
}

public void execute(JellyBeanViewModel viewModel) {
JellyBean data = getJellyBeanData(viewModel);
repo.saveBean(data);
}

JellyBean getJellyBeanData(JellyBeanViewModel viewModel){
String colorString = String.format("#%06X", (0xFFFFFF & viewModel.getColor()));
JellyBean data = new JellyBean(viewModel.getFlavor(), colorString);
return data;
}
}

Having each Interactor as a separate class is useful not only in order to keep things in order, but also for reusability. For instance, when I’ll create some Edit Jelly Bean screen, its UI can be completely different, but still I can use the same use case to save the changes to the Repository.

What’s happening on the Data Layer end?

I want to use a local database to save my jelly beans.

This is a recommended practice, so even between sessions you can have data to present to your users.

To easily do that we’ll be using Room, which wraps the good old SQLite DB that Android works with. What does it do for us?

  • Generates lots of boilerplate code
  • Verifies queries at compile time
  • Makes sure the db calls are not on the main thread.
  • and some more cool things for us that we’ll soon explore.

So my Repository looks something like:

public class JellyBeanRepo implements IJellyBeanRepo {

//...

@Override
public LiveData<List<JellyBean>> getJellyBeans() {
return appDatabase.jellyBeanDao().getAllJellyBeans();
}

@Override
public LiveData<JellyBean> getJellyBean(String id) {
return appDatabase.jellyBeanDao().getJellyBeanById(id);
}

@Override
public void saveBean(JellyBean jellyBean) {
appDatabase.jellyBeanDao().saveBean(jellyBean);
//sync with server...
}
}

Now Let’s create the JellyBean object, the way it will be saved on the DB.

We should annotate the class with @Entity, and one field as @Primary key.

@Entity
public class JellyBean {
@PrimaryKey String id;
String flavor;
String color;

Next, we’ll create a Dao — Data Access Object, which is an interface which defines the contract by which we’ll use the database. In other words, it defines what can we do with the database and how. The class should be annotated with @Dao, and each method will be annotated with the annotation which fits the SQL method we want to perform.

@Dao
public interface JellyBeanDao {
@Insert(onConflict = REPLACE)
void insertJellyBeans(List<JellyBean> jellyBeans);
@Query("select * from jellyBean")
LiveData<List<JellyBean>> getAllJellyBeans();

@Query("select * from jellyBean where id = :beanId")
LiveData<JellyBean> getJellyBeanById(String beanId);

@Insert(onConflict = REPLACE)
void insertJellyBean(JellyBean jellyBean);

@Delete
void deleteJellyBean(JellyBean jellyBean);

@Query("delete from jellyBean where id = :beanId")
void deleteJellyBeanById(String beanId);

}

Notice that the table that will be constructed matches the Entity class name (here: JellyBean).

To add the jelly bean to the DB we’ll use the insertJellyBean(). (We’ll talk later some more about the other queries).

Last thing to do would be to create the RoomDatabase.

@Database(entities = {JellyBean.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase instance;

public abstract JellyBeanDao jellyBeanDao();

public static AppDatabase getInMemoryDatabase(Context appContext) {
if (instance == null) {
instance = Room.inMemoryDatabaseBuilder(
appContext, AppDatabase.class).build();
}
return instance;
}

public static void destroyInstance() {
instance = null;
}

}

So what did we have today?

We created a SaveJellyBean interactor, that can take a JellyBeanViewModel that is used for presentation, process it to a JellyBean data model that the JellyBeanRepo can use. Then, we saw how to save the data model in a local database using Room.

Hope the basics of the architecture layers and components are now clearer. Next time we’ll see a little more complex example. See you there :)

❤️️ XOXO ❤️️

--

--

Product manager @ Facebook. Formerly: DevRel / DevX ; Google Developer Expert; Women Techmakers Israel community lead.