Applications architecture was neglected by official guidelines for Android for a very long time. In spite of that, interest in architecture among members of Android community has been growing steadily over the years.

Official guidelines and tools slowly caught up with the community interests, culminating in the recent announcement of a set of libraries called Android Architecture Components by Google. While the motivation behind Android Architecture Components is clear, the relationship between these libraries and application architecture is not so evident.

In this session Vasiliy Zukanov helps us understand what software architecture is and what it isn’t, and discuss several potential pitfalls associated with Android Architecture Components.

Introduction

My name is Vasiliy, and I work as an independent software consultant and freelance developer. I’m here to talk about architecture components and show you ways in which those components might harm your architecture.

What is Architecture?

Before we come to talk to about architecture components, we need to understand what architecture is, in particular, what is considered an architecture decision. Could it be:

Whether the application is expected to function online or offline?

When packaging an application, how should you distribute top-level packages in the codebase?

Dependency injection.

Unit testing.

Approaches to Architecture

This led to the emergence of several approaches to application architecture. One is architect everything - this was very popular in the 90s and early 2000s. The other extreme is to leave no time for architecture at all. Another approach is to architect some things.

Get more development news like this

The first two approaches are not good because they are extreme. We probably want to have is something. Architecting something in our applications and not over-architecting and not under-architecting. But how do we define this something? How do we find what we need to architect?

Architecture in Relation to Change

Architecture is our attempt to manage risks involved in changing requirements. As with any other risk-management activity, we cannot be prepared for all the possible risk that can come into play. We need to choose a subset of possible future changes that we are optimizing our application for, and this subset will constitute the application architecture.

Architecture and Libraries

How do the different libraries we might in our applications fit in this context? Can libraries become part of our application’s architecture? The answer is yes if the libraries that we integrate into our applications address the core domain problems that we are going to solve.

A library that addresses core concepts of our applications is third-party login libraries. Once we decide that we want to support Google login, we need to make changes in the Clients, we need to make changes on the server, and we cannot easily reverse this decision.

In most cases, however, libraries should not become part of an application’s architecture.

Android Architecture Components

Are Android architecture Components part of an application’s architecture? Android architecture Components is a set of libraries which provide very general functionality but is not related to any specific business domain. Therefore, these libraries should not become part of your application’s architecture.

LiveData

In order to understand what LiveData is we need to get back to our usual and standard, Observer design pattern.

We have three blank entities in the Observer design pattern. Client, which is interested in some events that happened in the Observable; and the Observer interface, which Client implements.

The Client registers itself with the Observable, and the Observable notifies the Client through this interface when the events, that the Client was interested in, happen. This is probably one of the most used design patterns in the world.

What’s LiveData, then? We start with the same three classes, but we add another one, called LiveData. The Client still implements the Observer interface, but instead of directly registering itself with the Observable, the Client gets a reference to LiveData from the Observable and then registers itself with the LiveData.

The Observable then pushes data changes into the LiveData and the LiveData is the one responsible for notifying the Client.

The Observer interface is no longer under our control. Before that, when we implement our custom Observer design patterns, we decide how Observer interface looks. However, if we go LiveData way, Observer interface is a closed interface provided by the framework. We do not have control over this interface anymore.

Custom Observer Interface

If we use LiveData instead of regular Observer interface, Observer pattern, we will write less code. When we want to get just data from the Observable:

public interface ReverseGeocodeObserver { boolean onReverseGeocodeLocationChanged ( String location , long ageMs ); void onReverseGeocodeError ( int errorStatus ); }

This scheme lowers the risk of memory leaks. LiveData will take care of unregistering itself when the Observer dies, it comes at a cost: the scheme becomes much more complicated in regards to the Lifecycle itself.

public interface Observer < T > { void onChanged ( @Nullable T t ); }

LiveData will release us from understanding and handle Lifecycle of activities and fragments. However, in practice, I would argue that it does not achieve that and only complicates the matter. For example, if you look at the interface, at the API of LiveData, you will notice that it has several methods for registration. One method takes LifecycleOwner as a parameter and the other method, ObserveForever, takes no LifecycleOwner as a parameter.

LiveData also means loss of a control over Observer interface. When we implemented the Custom Observer Pattern, we had total control over Observer interface.

LiveData sits at the interface of your Observable. If you want to refactor it to the usual Observer design pattern, you will need to change your tests and refactor the codebase, and take care of bugs that slipped in between.

ViewModel

ViewModel has nothing in common with MVVM architectural pattern. It is misleadingly named and does not describe its real intent: a ViewModel is kept upon configuration change, so it is an Object, which is logically scoped to activity and fragment.

Configuration change without ViewModel

This is how Android developers handled configuration changes before ViewModel:

public class MyActivity extends Activity { private String mUserId ; @Override protected void onCreate ( @Nullable Bundle savedInstanceState ) { super . onCreate ( savedInstanceState ); if ( savedInstanceState != null ) { mUserId = savedInstanceState . getString ( "user_id" ); } } @Override protected void onSaveInstanceState ( Bundle outState ) { super . onSaveInstanceState ( outState ); outState . putString ( "user_id" , mUserId ); } }

Questioning Motivation Behind ViewModel

ViewModel configuration change had been handled by saving and restoring the state, and ViewModel is a simpler solution. ViewModel comes as an additional a solution to the previous mechanism, as opposed to a replacement.

The amount of applications that need to handle screen rotation is fewer than you think. This is the formula I like to have a developer ask: Start with your Home screen and swipe on the screen. Count the number of applications that you routinely interact with in landscape mode, divide them by the total number of application that you counted, that you have on this main screen.

That resulting percentage is the fraction of application that needs to take care of rotation for you as a user.

Optimizing for rotation is generally a waste of time, and I will wait for some analytics from my users to decide if this is necessary.

ViewModel Pros and Cons

ViewModel allows us to handle configuration change more easily. But the disadvantage is the additional effort and the increased chance of getting a save and restore bug.

Suppose there is a reference to contact subject inside our ViewModel, that’s a memory leak. That is something that we hadn’t, we didn’t need to deal with beforehand. And the reason why we have this memory is the ViewModel does not simplify the LifeCycle. ViewModel takes the LifeCycle of the activity, adds on top of it LifeCycle of the ViewModel itself, and forces you to think about two LifeCycles. If you do not, you will end up having a memory leak.

Summary

Android architecture Components are not architecture components. They are design components at best. If you use them, you may increase the complexity of your codebase, for a questionable gain.

There’s really no evident reason to use them. In my opinion, they address the almost non-existent problem. And surprisingly, if you use them, they will make your future changes that you might want to make in response to future requirements changes, they will make these changes harder.

Lastly, this is my blog if you want to read some additional interesting and, in many cases, controversial ideas, including a related post on Android Architecture Components.