Exploring the new Android Architecture Components library

What an I/O! There were so many exciting things announced this year and too much that I want to write about! One of the topics that I (and a lot of other people) were hyped for was on Architecture. From these talks Architecture Components was announced, and after playing with them since, I’m taking this time to share my experiences with them 🤘


What are Architecture Components?

Architecture Components is a new library by Google that has the aim to help us design application that are “robust, testable, and maintainable”. In a nutshell, this library helps us to better handle the persisting of data across lifecycle events and configuration changes, whilst also helping us to create a better architecture application and avoid bloated classes that are difficult to maintain and test.

Sounds like a pretty sweet package, right! 🍭 When this library was announced I was obviously curious, so I headed on over to the documentation and there’s a lot to take in at first. I put this together to visualise the different classes, interfaces and more that are involved:

Don’t be overwhelmed by this, everything is organised in this fashion so that each concept here has a clear responsibility, so it’s pretty easy to follow when you get stuck into it. Let’s start at the top and take a journey through what each of these classes is responsible for 🙂


Lifecycle

As per the documentation, Lifecycle is an abstract class which has an Android Lifecycle attached to it —and, because it is holding this state of the lifecycle, objects can observe this state and react accordingly. In order to keep a track of this state, there are two fundamental concepts (represented as Enums) that the Lifecycle class incorporates:

Event

An event is the lifecycle event that has been trigged by a lifecycle change (such as resuming an activity). In our classes we can implement callbacks for each of these events so that we can handle lifecycle changes. The arch.lifecycle package provides an Annotation for us to use which means we can annotate the methods within our class which should be triggered upon certain lifecycle events.

Without going into implementation detail just yet, this will look like so:

@OnLifecycleEvent(ON_STOP)

We can also handle multiple lifecycle events within the same annotated method, this allows us to trigger a specific method based off multiple lifecycle events being triggered:

@OnLifecycleEvent({ON_STOP, ON_START})

This approach is handy as it means adopting this into your application allows you to annotate the methods that you may already be using for handling lifecycle events, meaning we don’t have to re-write much of our implementation if we wish to incorporate the arch.lifecycle components into our app.

There is an event for several different lifecycle events that we can listen for, the ones triggered will be one of:

  • ON_ANY — This event will be triggered upon any lifecycle event. This allows you to react upon any change in the lifecycle of what is being observed.
  • ON_CREATE — This event will be triggered upon creation of the LifecycleOwner.
  • ON_DESTROY — This event will be triggered upon the LifecycleOwner being destroyed.
  • ON_PAUSE — This event will be triggered upon the LifecycleOwner being paused.
  • ON_RESUME — This event will be triggered upon the LifecycleOwner being resumed.
  • ON_START — This event will be triggered upon the LifecycleOwner being started.
  • ON_STOP — This event will be triggered upon the LifecycleOwner being stopped.

As you can see, it doesn’t give us access to every single lifecycle event that may be triggered within an activity or fragment — but it’s enough to give us the ability to manage the lifecycle of views and other components with ease and efficiency.

Creational lifecycle events (ON_CREATE, ON_RESUME, ON_START) will all be called after the LifeCycleOwner corresponding method has returned. Whilst teardown lifecycle events (ON_DESTROY, ON_PAUSE, ON_STOP) will be call before the LifeCycleOwner corresponding method has been called. This is so that we can handle these lifecycle events once our activity/fragment has handled any setup and also before it is destroyed.

State

The state of a Lifecycle is essentially the point is is at between two lifecycle events. After an event is triggered the Lifecycle will enter one state, then leaving it and entered another state when another event is triggered.

As you can see from the above diagram, our Lifecycle instance can be in one of five states at any given time, these are:

  • CREATED — The state of the lifecycle after the ON_CREATE event but before the ON_START event. It is also in this state after the ON_STOP event and before ON_DESTROY.
  • DESTROYED — The state of the lifecycle after the ON_DESTROY event.
  • INITIALISED — The state of the lifecycle at the initial starting point.
  • RESUMED — The state of the lifecycle after the ON_RESUME event.
  • STARTED — The state of the lifecycle after the ON_START and before the ON_RESUME event. It is also in this state after the ON_PAUSE event and before ON_STOP.

If we want to manually fetch the current state of our Lifecycle instance then we can do so by using its getCurrentState() method, this allows us to retrieve its current state at any given time. This may be useful for checking the state of a Lifecycle component before performing some form of operation. A State also has an isAtLeast() method that we can use to perform comparisons against the current lifecycle state.


Now we know a little bit more about the State and Event enums that the Lifecycle incorporates, we can now take a look at the few different ways in which we can interact with the class:

You’ll notice in this simple diagram the two core methods of the Lifecycle class that we can use for Observable management:

  • addObserver() — Using this method we can add an Observer instance that will be notified whenever our LifecycleOwner changes state. When we add an Observer, it will receive all events leading up to the current state. So for example, if our Lifecycle is currently in RESUMED then the Observer will receive ON_CREATE, ON_START and ON_RESUME events.
  • removeObserver() — This method can be called to remove a given observer from the list of observers for the Lifecycle. Removing an Observer from a Lifecycle will mean that it will no longer receive any triggered events.

We also have access to the getCurrentState() method which returns the current state in which the Lifecycle is in. This is useful for when you may want to only perform a certain operation when the Lifecycle is in a certain State.

But where does this Lifecycle class fit in with our activity, and how can we implement it? This is where the LifecycleOwner interface comes in to play. Let’s take a step down in our Diagram and take a look at this interface.

LifecycleOwner

The LifecycleOwner is an interface that our lifecycle aware concept (such as an Activity or Fragment) can implement in-order to be able to retrieve a Lifecycle instance for that class and allow it to be used by anything that needs to be lifecycle aware in context to that concept. This is a powerful piece of functionality as it allows our components to be more independent — our activity / fragment no longer needs take care of setup and clean-up operations for other classes. In turn, this gives us a greater separation of concerns with our classes, making our code easier to maintain and test 🙌🏻

The LifecycleOwner only has a single method, which is essentially used to say “Hey, I’m a lifecycle-aware component — I have a Lifecycle!”. However, one important thing to point out here is that there currently this note in the documentation regarding this component:

Note: Since Lifecycles project is in alpha stage, Fragment and AppCompatActivity classes cannot implement it (because we cannot add a dependency from a stable component to an unstable API). Until Lifecycle is stable, LifecycleActivity and LifecycleFragment classes are provided for convenience. After the Lifecycles project is released, support library fragments and activities will implement the LifecycleOwnerinterface; LifecycleActivity and LifecycleFragment will be deprecated at that time. Also, see Implementing LifecycleOwner in Custom Activities and Fragments.

I’ll update this article once the above changes, but just bear this in mind for the implementations that you carry out. So for now, you’ll need to use the above classes if you wish to implement this functionality into your app.


So, if we were to go ahead and implement this LifecycleOwner (LifecycleActivity at the moment as per the note above), we can simply extend the LifecycleActivity class like so:

class MainActivity: LifecycleActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

Now that we’re extending this class, we have access to the getLifecycle() method which is exposed by the LifecycleOwner interface. This method allows us to fetch the Lifecycle instance for our current activity and add an Observer so that we can observe lifecycle events. This can be done like so:

lifecycle.addObserver(SomeObserver())

or if you’re using Java:

getLifecycle().addObserver(new SomeObserver())

Here within our LifecycleOwner class we’re fetching it’s Lifecycle instance and then adding an Observer to it. This now means that we’re observing any lifecycle events that may be triggered. The Observer that we pass in is essentially a class that we create that needs to be extending the LifecycleObserver interface. This interface doesn’t declare any methods that we are required to implement, instead it relies on methods annotated with the OnLifecycleEvent annotations. This could look something like below:

inner class SomeObserver: LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() { }

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

Here I’ve defined a simple class that extends the LifecycleObserver interface and added method definitions with OnLifeCycleEvent annotations for the lifecycle Events which I wish to observe.

Now that our interface implementation will receive the lifecycle events that are triggered, activity here can react accordingly.


Now that we’ve got a better understanding of how the Lifecycle and LifecycleComponent components work, we’re able to better handle the configuration changes better in our app. But that’s not enough alone, next we’re going to take a look at the ViewModel component which when used together with these previous sections, gives us the ability to store and manage UI related data within an encapsulated class.

ViewModel

The ViewModel class is a component that is designed to survive configuration changes — it’s responsible for the data that is is displaying it terms of both storage and management. This means that our activity or fragment no longer needs to have the added responsibility of retaining this state as the ViewModel takes on this responsibility. And what’s even better is that it is automatically retained upon configuration changes, so the hard work is done for us 😁

The ViewModel class itself doesn’t actually require too much input from our side, it’s the LiveData instance which is placed inside it where most of the magic happens (we’ll go onto that next) 🎩

When a ViewModel is no longer needed (at the end of its scoped lifecycle) it has a method called onCleared() that will be automatically called to clear up the ViewModel before it is destroyed — such as removing a subscription to data to prevent a memory leak. The destroying of a ViewModel will occur in cases such as an activity being destroyed or a fragment being detached and so on.


If we wanted to declare a new ViewModel for use, then we can create something like below:

public class MyViewModel: ViewModel {
private val users: LiveData<List<String>> = 
MutableLiveData<List<String>>()

fun getUsers(): LiveData<List<String>> {
if (users.value == null) loadUsers()
return users
}

private fun loadUsers() {
// do async operation to fetch users
}
}

This class is doing a couple of things, so let’s step through them:

  • To begin with, we declare an instance of the LiveData class, which we will look at in the next section.
  • Next we implement a method getProfile(). This provides a way for our activity/fragment to access the LiveData instance that our view model is holding a reference to. Once fetched, our activity/fragment can use the LiveData instances’ observe() method to observe for changes in that data.
  • Finally, our ViewModel implements some method that is used to fetch the data that is it responsible for. In this case, this could be fetching the Profile data that it requires from the cache.

It’s important to note that the ViewModel may outlive some activity/fragment instances that it is involved with — because of this it’s important to not keep a reference to anything that holds an activity Context (such as a View) as you may encounter memory leaks.

In the scenario where you may need a reference to the application Context you may wish to use the AndroidViewModel class instead, as this classes constructor receives the Application instance in the constructor. When using this class, you can fetch the application instance via the use of its getApplication() method.

Using ViewModels

Now we understand a little more about what a ViewModel is, we need to be able to fetch the ViewModel instances with our activity/fragment. For this, we have access to a helper class known as ViewModelProviders — in our activity/fragment, this would look something like this:

val profileViewModel =   
    ViewModelProviders.of(this).get(SomeViewModel::class.java)

The ViewModelProviders class acts as a utilities class for what is known as a ViewModelStore. It’s essentially a Factory that has methods we can use to create a ViewModelProvider instance that will live for either:

  • The lifecycle of an activity, using ViewModelProviders.of(someFragmentActivity)
  • The lifecycle of a fragment, using ViewModelProviders.of(fragment)

Once we’ve retrieved our instance of a ViewModelProvider using this of() method, we have access access to provide ViewModels to our containing class. But how do the internals work for this providing? Well, the ViewModelProvider class holds a instance of a ViewModelStore — this ViewModelStore is responsible for storing any ViewModels that we create within the ViewModelStore lifetime.

We can use the ViewModelProvider’s get() method to retrieve an instance of the ViewModel type that we pass in as a parameter. If we dig into the source code here, the get() method looks like so:

https://gist.github.com/hitherejoe/12b75b6ab5e0238d04305033b429b22b

As you can see, when this get() method is called, the ViewModelProvider will check whether the ViewModelStore already has an existing ViewModel for that class type and if so, it will be returned. However, if one doesn’t already exist then a new ViewModel will be created and added to the ViewModelStore — this ViewModel will be associated with the given scope. There’s nothing complicated or fancy going on here!

Now that we are able to fetch an instance of our ViewModel using this get() method, we need to do something with it! If we go back to the ProfileViewModel class that we defined above, then we can use the methods we defined to fetch and observe the response.

someViewModel.getUsers().observe(this, observer)

In this call, a few things go on behind the scenes:

  • We use our getProfile() method to kick of an asynchronous operation, in this case we’re fetching a profile from the API
  • We call the observe method (which belongs to the LiveData class) and pass in our LifeCycleOwner (this, which in the case here would be our Activity) and also a callback to handle the result when a profile is returned.

But why use ViewModels? There’s some great advantages that we can gain from doing so:

  • Separation of concerns — To begin with, our activity is not only no longer responsible for the requests for fetching this Profile instance from some data source, but it also removes the responsibility of keeping a reference to this Profile instance to restore upon configuration changes. Both of these responsibilities have been shifted to the ViewModel class.
  • Less maintenance during clean-up — When our activity is responsible for the above, we can have a lot of code in place that is used for the cleaning up of these requests. But thanks to the ViewModels onCleared() method, these resources are automatically cleared when called by the system. This call happens when the owner of the Activity is finished, which is when its assigned lifecycle comes to an end.
  • Less bloated classes — Because of this shift of responsibilities, we remove a lot of code that is used to handle requests, state persistence and tear-down. This kind of code can often lead to god-activities, which makes our code hard to extend and maintain. Using ViewModels can help us to move a lot of this code and have more focused classes.
  • Easier testing — Because you’ll also have tests for all this too (I hope 😉), not only will it be easier to test these responsibilities but it will also result in more fine-grained test classes too. More focused application classes = more focused test classes.

LiveData

The LiveData class provides us with the ability to hold a piece of given data and also observe it across lifecycle changes. The class provides us with a collection of methods that can be utilised to manage any observers that are in place for the data being held within the class.

If you’ve checked out the documentation for LiveData, you’ll have noticed that there’s a lot of difference situations which LiveData can be in that can alter the way in which decides when / if data is emitted.

To get a better understanding of all these different situations, let’s walk through this step-by-step so we know exactly what is going on here:

  • When we call setValue() the value of the live data will be set. At this point, if there are active observers for our LiveData class AND the Lifecycle of our LifecycleOwner is in an ACTIVE state, then the value will be emitted to those observers. This isn’t externally available for LiveData classes, you must use the MutableLiveData if you wish to access this protected method.
  • When we call observe() there are a few different cases that can occur. First of all, if the LifecycleOwner is in the DESTROYED state then this call will be completely ignored. However, if it’s not DESTROYED then at this point the observer is added to the list of observers which is retained by the LiveData class. Using this method allows us to add an observer and tying the life-span to the corresponding Lifecycle — this means that if the state if that Lifecycle reaches DESTROYED, then the observer will automatically be removed — meaning we can easily avoid memory leaks from these situations.
  • At this point, if there is already data set within the LiveData class then the observer will receive this latest value as an emission.
  • Next, the onActive() method will be triggered if we have more than zero Observers attached to our LiveData instance. Here, we should start performing operations that keep our data up-to-date as this callback signals that the instance is in use.
  • However, if we decide to use the removeObserver() method to well, remove an observer, then the given observer will be removed from the list of Observers. At this point, if we hit < 1 Observers for our LiveData instance then this means there are no Observers left whose lifecycle is either STARTED or RESUMED. The instance could still have inactive observers, it just means that their lifecycle hasn’t triggered ON_START or ON_RESUME yet so observing for data changes doesn’t make any sense.
  • We can also manually check the active state of observers within our LiveData instance by using the hasActiveObservers() method.

As well as this flow, we also have access to two more methods in the LiveData class:


Going back to the MutableLiveData class mentioned above, the difference here is that this gives us access to two public methods. The first being setValue() which allows us to manually seta value of the instance and automatically notify any observers of this change. This call must be made on the main thread — if you need to carry this action out on the background thread, then you can use the second exposed method, postValue(), to post a value change to the main thread.

Conclusion

I know that is a lot to take in, but I hope this article has helped to break down the Architecture Components library a bit more. Once you’ve processed all of this, if you haven’t already then I hope you can see the advantages that it can bring your application.

I think an important thing to note (and something that I try to point out when sharing learnings) is that we don’t (and shouldn’t feel like) we need to jump on and change our entire implementation to use this cool new library. It’s first important to learn how the internals work and then think about how it fits in with your application. You may already have something in place that works great for what you have, and you may want to leave that in place. But if Architecture Components is something that you feel you’d benefit from then the great thing is that you can implement it as you go for new features, it’s not an all-or-nothing situation (although the all would be pretty nice 😁).

If you’re not too busy having fun with the library then I’d love to hear your thoughts on it or about how you’re using it already. Do drop me a tweet 🐧

You can also check out more of my projects here:


Resources

Here are some great resources if you’re wishing to learn some more 💻

  • Google I/O Videos:


  • Google Sample Architecture Components Sample

  • Architecture components sample

  • Android Architecture Components Codelab

Leave a Reply

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