Android Application Architecture
An application architecture is a consistent plan that needs to be made before the development process starts. This plan provides a map of how the different application components should be organized and tied together. It presents guidelines that should be followed during the development process and forces some sacrifices that in the end will help you to construct a well-written application that’s more testable, expandable, and maintainable.
Good architecture takes many factors into consideration, especially the system characteristics and limits. There are many different architectural solutions out there, all of them with pros and cons. However, some key concepts are common between all visions.
Until the last Google I/O, the Android system didn’t recommend any specific Architecture for application development. That means that you were completely free to adopt any model out there: MVP, MVC, MVPP, or even no pattern at all. On top of that, the Android framework didn’t even provide native solutions for problems created by the system itself, specifically the component’s lifecycle.
So, if we wanted to adopt a Model View Presenter pattern on your application, we needed to come up with your own solution from scratch, writing a lot of boilerplate code, or adopt a library without official support. And that absence of standards created a lot of poorly written applications, with codebases that were hard to maintain and test.
After 12 long years, the Android team finally decided to listen to our complaints and to help us with this problem.
2. Android Architecture
The new Android Architechture Guide defines some key principles that a good Android application should conform to and also proposes a secure path for the developer to create a good app.According to the guide, a good Android application should provide a solid separation of concerns and drive the UI from a model. Any code that does not handle a UI or operating system interaction should not be in an Activity or Fragment, because keeping them as clean as possible will allow you to avoid many lifecycle-related problems. After all, the system can destroy Activities or Fragments at any time. Also, the data should be handled by models that are isolated from the UI, and consequently from lifecycle issues.
The New Recommended Architecture
The architecture that Android is recommending cannot be easily labeled among the standard patterns that we know. It looks like a Model View Controller pattern, but it is so closely tied to the system’s architecture that it’s hard to label each element using the known conventions. This isn’t relevant, though, as the important thing is that it relies on the new Architecture Components to create a separation of concerns, with excellent testability and maintainability. And better yet, it is easy to implement.
To understand what the Android team is proposing, we have to know all the elements of the Architecture Components, since they are the ones that will do the heavy lifting for us. There are four components, each with a specific role: Room
, ViewModel
, LiveData
, and Lifecycle
. All of those parts have their own responsibilities, and they work together to create a solid architecture. Let's take a look at a simplified diagram of the proposed architecture to understand it better.
As we can see, we have three main elements, each one with its responsibility.
- The
Activity
andFragment
represent theView
layer, which doesn't deal with business logic and complex operations. It only configures the view, handles the user interaction, and most importantly, observes and exhibitsLiveData
elements taken from theViewModel
. - The
ViewModel
automatically observes theLifecycle
state of the view, maintaining consistency during configuration changes and other Android lifecycle events. It is also demanded by the view to fetch data from theRepository
, which is provided as observableLiveData
. It is important to understand that theViewModel
never references theView
directly and that the updates on the data are always done by theLiveData
entity. - The
Repository
isn't a special Android component. It is a simple class, without any particular implementation, which is responsible for fetching data from all available sources, from a database to web services. It handles all this data, generally transforming them to observableLiveData
and making them available to theViewModel
. - The
Room
database is an SQLite mapping library that facilitates the process of dealing with a database. It automatically writes a ton of boilerplate, checks errors at compile time, and best of all, it can directly return queries with observableLiveData
.
The Observer Pattern is one of the bases of the LiveData
element and Lifecycle
aware components. This pattern allows an object to notify a list of observers about any changes on its state or data. So when an Activity observes a LiveData
entity, it will receive updates when that data undergoes any kind of modification.
Another Android recommendation is to consolidate its architecture using a Dependency Injection system, like Google’s Dagger 2 or using the Service Locator pattern.
Architecture Components
We must dive deep into the aspects of the new components to be able to really understand and adopt this model of architecture. Due to the complexity of each element, in this article, we’ll only talk about the general idea behind each one and look at some simplified code snippets. We’ll try to cover enough ground to present the components and get you started. But fear not, because future articles in this series will dig deep and cover all the particularities of the Architecture Components.
Lifecycle-Aware Components
Most of the Android app components have lifecycles attached to them, which are managed directly by the system itself. Until recently it was up to the developer to monitor the components’ state and act accordingly, initializing and ending tasks at the appropriate time. However, it was really easy to get confused and make mistakes related to this type of operation. But the android.arch.lifecycle
package changed all that.
Now, Activities and Fragments have a Lifecycle
object attached to them that can be observed by LifecycleObserver
classes, like a ViewModel
or any object that implements this interface. That means that the observer will receive updates about the state changes of the object that it is observing, like when an Activity is paused or when it is starting. It can also check the current state of the observed object. So it's much easier now to handle operations that must consider the framework lifecycles.
For now, to create an Activity
or Fragment
that conforms to this new standard, you have to extend a LifecycleActivity
or LifecycleFragment
. However, it's possible that this won't always be necessary, since the Android team is aiming to completely integrate these new tools with its framework.
The LifecycleObserver
receives Lifecycle
events and can react through annotation. No method override is necessary.
The LiveData
Component
The LiveData
component is a data holder that contains a value that can be observed. Given that the observer has provided a Lifecycle
during the LiveData
instantiation, LiveData
will behave according to Lifecycle
state. If the observer's Lifecycle
state is STARTED
or RESUMED
, the observer is active
; otherwise, it is inactive
.
LiveData
knows when the data was changed and also if the observer is active
and should receive an update. Another interesting characteristic of the LiveData
is that it's capable of removing the observer if it's in a Lifecycle.State.DESTROYED
state, avoiding memory leaks when observed by Activities and Fragments.
The ViewModel
Component
One of the most important classes of the new Architecture Components is the ViewModel
, which is designed to hold data that is related to the UI, maintaining its integrity during configuration changes like screen rotations. The ViewModel
is able to talk with the Repository
, getting LiveData
from it and making it available in turn to be observed by the view. ViewModel
also won't need to make new calls to the Repository
after configuration changes, which optimizes the code a lot.
The Room
Component
Android supported SQLite from the start; however, to make it work, it was always necessary to write a lot of boilerplate. Also, SQLite didn’t save POJOs (plain-old Java objects), and didn’t check queries at compile time. Along comes Room
to solve these issues! It is an SQLite mapping library, capable of persisting Java POJOs, directly converting queries to objects, checking errors at compile time, and producing LiveData
observables from query results. Room
is an Object Relational Mapping library with some cool Android extras.
Until now, you could do most of what Room
is capable of using other ORM Android libraries. However, none of them are officially supported and, most importantly, they can't produce LifeData
results. The Room
library fits perfectly as the persistent layer on the proposed Android Architecture.
To create a Room
database, you'll need an @Entity
to persist, which can be any Java POJO, a @Dao
interface to make queries and input/output operations, and a @Database
abstract class that must extend RoomDatabase
.
Conclusion
The standardized architecture proposed by Android involves a lot of concepts. We can’t expect to have a complete understanding of this topic yet. After all, we’re merely introducing the theme. But we certainly have enough knowledge by now to understand the logic behind the architecture and the roles of the different Architecture Components.