Android Architecture Components

Piotr Rut

We all in principle try to perfect our app. Choosing best architecture and frameworks, meticulously planning classes and utilizing design patterns to make best of our work. But with nearly every line written, functionalities become replicated once more.

Nowadays when you write another app you probably want (and should!) to reach for tested and reliable solution to save yourself a significant amount of work. Fortunately Google is aware of typical developer’s tasks and comes forth with package of solutions called Android Architecture Components.

Why should you choose Android Architecture Components over other frameworks?

Well, I am not saying that you should always strive to use official solutions. There are probably many corner cases where other framework will be better fit. If you see downsides and limitations forced on you by using Android Architecture Components I will assume you dug deep enough to know better what you are doing. But! What I’m trying to say is that they should be your first pick. There are many, many libraries written to solve same issues. One thing that you can be sure of is that going for Android Architecture Components will give you an official support, tons of documentations and stack posts, you can rely on Google engineers experience, expect new features to show up and be sure that even if there is a bug somewhere, somebody will fix it pretty soon and publish corrected version. This really means a lot if you are planning to release you app to market and maintain it for quite some time.

What actually are these components?

Handling lifecycle

Managing lifecycles of objects is not an easy task especially when you are dealing with UI and can’t anticipate user’s actions. There are many activities, fragments, services and you need to make sure they are properly synchronized between each other, receive proper data when recreated and work smoothly to not spoil user’s experience.

Android Architecture Components answers that need providing lifecycle mechanism. Their design is pretty straightforward as is based on observer pattern.  Lifecycle class has two enumerations, one representing events like changing state of the object and another showing current state.  With observer pattern you are sure that everything happens when it should without leaving space to some nasty race conditions.

This code snippet show how simple is that

public class UpdateObserver implements DefaultLifecycleObserver  {
    @Override
    public void onStop(LifecycleOwner owner) {
        // pause update
    }

    @Override
    public void onStart(LifecycleOwner owner) {
        // start update download code
    }
}

To observe events you need to implement DefaultLifecycleObserver and properly override its methods.

Before Java 8 things looked a bit different as you were forced to implement LifecycleObserver and use annotation to mark methods as events handles.

public class UpdateObserver implements LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void downloadUpdate() {
        //...
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void stopDownloadingUpdate() {
        //...
    }
}

On the other end we need observed object conforming LifecycleOwner interface which has only one method getLifecycle. Fragment and FragmentActivity already implement LifecycleOwner interface but you are free to create your own subject class.

Room

Another component is object-relational mapping library for SQLite called Room which I personally really liked. It is rapidly gaining on popularity despite many competitors thanks to being official and well-designed solution.

First thing that needs to be done is to create Room database class which will contain holder to SQlite database and act as an access points to underlying data. It has to inherit RoomDatabase and have @Database annotation.  Via entities parameter in @Database we specify which classes will be used to create tables.  Optionally we can add some custom type converters, this way we won’t have to worry about storing “non-standard” data later on. Last thing worth noticing is that we create abstract method for each data access object we want to have.

@Database(entities = {Project.class, User.class}, version = 1)
@TypeConverters({DateConverter.class})
public abstract class RoomDBInstance extends RoomDatabase {

    private static RoomDBInstance sInstance;

    @VisibleForTesting
    public static final String DATABASE_NAME = "testDB";

    public abstract ProjectDao projectDao();

    public abstract UserDao userDao();

@Entity marks a class that will have a mappig table in SQLite database. All that needs to be done is to mark primary key with @PrimaryKey annotation. Other non transient fields will automatically have coresponding columns in table unless they are explicetly ignored. There are many more annotation modifiers which give you full control over table shape.

@Entity(tableName = "users")
public class User {
    
    @PrimaryKey
    @NonNull
    public int user_id;

    public String email;

    public String nazwa;

}

Concept of data access object might be a bit different from what you usually expect.  With annotations you can mark methods with commons database operations like @Insert, @Delete, @Update, but in addition there is @Query which takes a string with actual SQL query and can be used as a replacement for all of the above. I like this approach because even though writing queries is still needed you do not have to learn another API. Also correctness of queries is checked at compile time what saves you some (nerves) time on testing.

Another feature is @Transaction annotation. Database operations in this method will be run on single transaction, so if you have to run many queries be sure to use it. It is very useful when you want to perform some more complicated operation presumably requiring several steps.

@Transaction
public void insertProjectIfNotExist(Project project) {
    if (!projectExists(project.user_id, project.nazwa))
        insertProject(project);
}

In example above we firstly check if we already have such project in our database and if not we insert it (hint: this just example and such operation could probably be done with proper OnConflictStrategy applied to @Insert query).

@Dao
public abstract class UserDao {

    @Insert(onConflict = OnConflictStrategy.ROLLBACK)
    public abstract void insertUser(User newUser);

    @Query("DELETE FROM users")
    public abstract void deleteAllUsers();

    @Query("SELECT * FROM users WHERE user_id = :id")
    public abstract User getUserById(int id);
}

As we jumped to performance Room is at the forefront. Overhead is minimal so times are very close to proper SQLite code. Below you will see collation of measured times for popular Android ORMs.

I showed here balanced variant of measurements (you can get more detailed info about measurement methods under this link). It is worth to mention that additional SQLite optimizations like caching were disabled by author and could have some impact on results. Despite that it is clear that Room is in the forefront in terms of speed. Some framework perform especially well in specific scenarios (like writing with Realm) thanks to their varied mechanics, but this usually comes with downsides during different operations like excessive memory usage or underperformance (for Realm that would update and delete according to charts). My conclusion would be that there is no universal solution and you should carefully choose the best ORM based on what kind of database operations will be most common for your application.

ViewModel

Model–view–viewmodel (MVVM) architectural pattern is similar to well-known model–view–presenter. The most remarkable distinction between view-model and presenter is that presenter has a reference to view whereas view-model does not. In MVVM view binds directly to view-model to update its data. Android has its own implementation of this pattern in form of ViewModel class. It was created to help developers store and manage UI related data in lifecycle-aware way. As a result there we have neat way to omit some unnecessary, overhead operations done in background and in general make user experience more fluent.

Let’s imagine such a situation. You have activity that presents user’s project. All projects are stored in database so to get one you need to create transaction and make a query. In addition to fully present project some additional parameters have to be calculated. Now user chooses a project and waits for view. The moment it shows up he decides that everything will look more readable after rotation and everything needs to be done again wasting resources and making things more complicated than they should be.

Luckily now we have another weapon in our arsenal called ViewModel.  This class is lifecycle aware. What it means is that it can live through configuration changes so you read exactly same data after rotation. Important thing to remember is that ViewModel does not replace onSavedInstanceState. When activity is killed by system you still will have to use it to retrieve your data. On the downside using ViewModel for simple UI might be an overkill as data biding between view and view-model tends to generate a lot of book-keeping data and can result in excessive memory consumption so it’s worth to take that into account.

LiveData

Next we will take a look at LiveData. This class is basically observable data holder, but what distinguishes it from regular observer is fact that it is lifecycle aware. It will only notify app components that are in active state.  Thanks to this feature we don’t need to check if data underneath changed as we will be notified every time. LiveData interacts nicely with other architecture components like ViewModel or Room. For example we can create query method in Room DAO returning LiveData.

@Query("SELECT * FROM projects WHERE user_id = :uid")
public abstract LiveData<List<Project>> getAllUserProjects(int uid);

This little code change has significant consequences. Now Room will call automatically and asynchronously this method from DAO whenever database changes and if there is observer for this data it will be notified. All this is a great setup for wall app which I mentioned at the beginning of this article. Now whenever someone posts something new, UI will know that it needs to update its content.

In example above LiveData works really well, but as I mentioned before if this solutions does not fit your case don’t force it. In my cases when I was storing only local data to my database and knew upfront that UI refresh will be needed so I decided to handle this with my own code. It solved problem with visible items reload after changing fragments. Of course it could be done still having LiveData in the picture, but the case was simple enough for me to feel safe with more “raw” solution.

Summary

If you are Android developer architecture components are a great way to tackle your everyday programming tasks. Becoming friendly with them will really make your life easier so don’t hesitate!

Sources:

https://github.com/AlexeyZatsepin/Android-ORM-benchmark

https://developer.android.com/topic/libraries/architecture/

https://blogs.msdn.microsoft.com/johngossman/2006/03/04/advantages-and-disadvantages-of-m-v-vm/

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami