Introduction to Android Architecture Components

Android was introduced to the world back in 2005, and during those 12 years of existence the platform has achieved amazing success, becoming the most-installed mobile OS. During that time, 14 different versions of the operating system have been launched, with Android always becoming more mature. However, a very important area of the platform continued to be ignored: a standard architecture pattern, capable of handling the platform peculiarities and simple enough to be understood and adopted by the average developer.

Well, better late than never. At the last Google I/O, the Android team finally decided to address this problem and respond to the feedback from developers all over the world, announcing an official recommendation for an Android Application Architecture and providing the building blocks to implement it: the new Architecture Components. And better yet, they managed to do it without compromising the openness of the system that we all know and love.

Architecture Components

In this tutorial, we’ll explore the standardized architecture proposed by the Android team at Google I/O and look at the main elements of the new Architecture Components: Lifecycle, ViewModel, LifeData, and Room. We won’t pay too much attention to the code, instead focusing on the concept and logic behind these themes. We’ll also take a look at some simple snippets, all of them written using Kotlin, an amazing language that is now officially supported by Android.

1. What Was Android Missing?

If you’re just beginning your journey as a developer, it’s possible that you don’t know exactly what I am talking about. After all, application architecture can be an obscure theme at first. But believe me, you will learn its importance soon enough! As an application grows and becomes more complex, its architecture will become more and more important. It can literally make your work a bliss or a living hell.

Application Architecture

Putting it roughly, 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 (generally related to more classes and boilerplate) that in the end will help you to construct a well-written application that’s more testable, expandable, and maintainable.

Software application architecture is the process of defining a structured solution that meets all of the technical and operational requirements, while optimizing common quality attributes such as performance, security, and manageability. It involves a series of decisions based on a wide range of factors, and each of these decisions can have considerable impact on the quality, performance, maintainability, and overall success of the application.

— Microsoft’s Software Architecture and Design Guide

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.

Old Mistakes

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.

Great news at the Google IO 2017

So, if you wanted to adopt a Model View Presenter pattern on your application, you 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. 

As I said, this situation has been criticized for years. In fact, I recently wrote about this problem and how to address it in my How to Adopt Model View Presenter on Android series. But the important thing is that 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 Architecture 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. However, the guide explicitly states that the route presented isn’t obligatory, and ultimately the decision is personal; it’s the developer who should decide which type of architecture to adopt.

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.

Android Architecture

As you can see, we have three main elements, each one with its responsibility. 

  1. The Activity and Fragment represent the View 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 exhibits LiveData elements taken from the ViewModel.
  2. The ViewModel automatically observes the Lifecycle 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 the Repository, which is provided as observable LiveData. It is important to understand that the ViewModel never references the View directly and that the updates on the data are always done by the LiveData entity.
  3. 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 observable LiveData and making them available to the ViewModel.
  4. 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 observable LiveData.

I’m sure you’ve noticed that we’ve talked a lot about observables. 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 (which is way simpler than DI, but without many of its advantages). We won’t cover DI or Service Locator in this tutorial, but Envato Tuts+ has some excellent tutorials about those themes. However, be advised that there are some particularities of working with Dagger 2 and Android Components that will be explained in the second part of this series.

3. 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. However, we won’t get into all the details in this tutorial. Due to the complexity of each element, in this tutorial, 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.

LifecycleObserver reacts to LifecycleEvents

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.

class MainActivity : LifecycleActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

The LifecycleObserver receives Lifecycle events and can react through annotation. No method override is necessary.

class MainActivityObserver : LifecycleObserver, AnkoLogger {
   @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
       info("onResume")
   }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        info("onPause")
    }
}

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.

LiveData value updating process

A LiveData must implement onActive and onInactive methods.

class LocationLiveData(context: Context)
    : LiveData<Location>(), AnkoLogger, LocationListener {
    private val locationManager: LocationManager =
            context.getSystemService(Context.LOCATION_SERVICE) as LocationManager

    override fun onActive() {
        info("onActive")
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, this)
    }

    override fun onInactive() {
        info("onInactive")
        locationManager.removeUpdates(this)
    }
    // ....
}

To observe a LiveData component, you must call observer(LifecycleOwner, Observer<T>).

class MainActivity : LifecycleActivity(), AnkoLogger {
    fun observeLocation() {
        val location = LocationLiveData(this)
        location.observe(this,
                Observer { location ->
                    info("location: $location")
                })
    }
}

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.

ViewModel is tight to UI Lifecycle

To create a view model, extend the ViewModel class.

class MainActivityViewModel : ViewModel() {

    private var notes: MutableLiveData<List<String>>? = null    
    fun getNotes(): LiveData<List<String>> {
        if (notes == null) {
            notes = MutableLiveData<List<String>>()
            loadNotes()
        }
        return notes!!
    }
    private fun loadNotes() {
        // do async operation to fetch notes
    }
}

To access from a view, you may call ViewProviders.of(Activity|Fragment).get(ViewModel::class). This factory method will return a new instance of the ViewModel or get the retained one, as appropriate. 

class MainActivity : LifecycleActivity(), AnkoLogger {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val viewModel = ViewModelProviders.of(this)
                .get(MainActivityViewModel::class.java)
        viewModel.getNotes().observe(
                this, Observer { 
                    notes -> info("notes: $notes")
                }
        )
    }
}

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.

@Entity
class Note {
    @PrimaryKey
    var id: Long? = null
    var text: String? = null
    var date: Long? = null
}
@Dao
interface NoteDAO {
    @Insert( onConflict = OnConflictStrategy.REPLACE )
    fun insertNote(note: Note): Long

    @Update( onConflict = OnConflictStrategy.REPLACE )
    fun updateNote(note: Note): Int

    @Delete
    fun deleteNote(note: Note): Int

    @Query("SELECT * FROM note")
    fun findAllNotes(): LiveData<Note>

    // on Kotlin the query arguments are renamed
    // to arg[N], being N the argument number.
    // on Java the arguments assume its original name
    @Query("SELECT * FROM note WHERE id = :arg0")
    fun findNoteById(id: Long): LiveData<Note>
}
@Database( entities = arrayOf(Note::class), version = 1)
abstract class Databse : RoomDatabase() {
    abstract fun noteDAO(): NoteDAO
}

Adding Architecture Components to Your Project

For now, to use the new Architecture Components, you’ll need to first add the Google repository to your build.gradle file.  For more details, see the official guide.

allprojects {
    repositories {
        jcenter()
        // Add Google repository
        maven { url 'https://maven.google.com' }
    }
}

Conclusion

As you can see, the standardized architecture proposed by Android involves a lot of concepts. Don’t expect to have a complete understanding of this topic yet. After all, we’re merely introducing the theme. But you certainly have enough knowledge by now to understand the logic behind the architecture and the roles of the different Architecture Components.

We talked about most of the topics related to the proposed Android architecture and its components; however, details about the Components implementation and some extras, like the Repository class and the Dagger 2 system cannot be covered by this first part. We’ll explore those themes in the next posts.

See you soon!

Leave a Reply

Your email address will not be published. Required fields are marked *