In the last part of this series, Introduction to Android Architecture Components, we talked about the new Android Architecture and why it was developed. Basically, the new architecture addresses some known Android issues by offering a bundle of components tailor-made for the system. These are the building blocks of the architecture. We've already had a quick look at these components, so now it is time to start to deep dive into them.
In this tutorial, we'll take a close look at the Lifecycle
and the LiveModel
components. While we're exploring these, we're also going to check out some code snippets from a sample application. Since we're talking about Android's new paradigms, the snippets are all made with the awesome Kotlin.
If you don't know Kotlin yet, please don't be afraid to follow along; the implementation is extremely close to Java, and I'm confident that you'll be able to understand it. If you want to learn more about Kotlin, Jessica Thornsby wrote an excellent series here on Tuts+ about Coding Android Apps in Kotlin. You should take a look!
Coding Functional Android Apps in Kotlin: Getting Started
Coding Functional Android Apps in Kotlin: Lambdas, Null Safety & More
1. The Sample Project
We provided a small application demonstrating the concepts that we're talking about in this tutorial. The app's name is MyWeatherApp, and it allows the user to fetch the day's weather using a city's name or the user's current location. The app's logic is quite simple, but you could improve on it to create your own application.
As you can see in the diagram below, the architecture conforms to the one proposed by Android, and we used the new Architecture Components package as much as possible, keeping things simple enough for a basic analysis. As a bonus, we're using Dagger 2 as a Dependency Injection library. However, we won't dig into much detail about its implementation, since it would escape the tutorial's scope.
How Does the App Work?
The application is as simple as possible. It has only one Activity, where the user can get the weather by searching a city's name or using the device's current location. The MainActivity
calls the MainModel
to get an observable LiveData
and reacts to it. The MainModel
fetches weather data from the MainRepository
, and consolidates all data as LiveData
. The MainRepository
gets its data from multiple sources.
Running the Sample App
Download or clone the repository from our GitHub repo and build with Gradle or open it in your IDE. You must also create an OpenWeatherMap account and get a new application ID. Add the application ID on a string resource called openWeather
.
<string name="openWeather">XXXXXXXXXXXXXXX</string>
2. Setting Up a Project
Since the Architecture Components are still in alpha, you have to include the Google repository, which contains some experimental libraries, in the project's build.gradle
.
allprojects { repositories { // add this repository maven { url 'https://maven.google.com' } } }
In the module build.gradle, add the following to the dependencies
section to add support for Lifecycles
, LiveData
, and ViewModel
:
compile "android.arch.lifecycle:runtime:1.0.0-alpha5"
compile "android.arch.lifecycle:extensions:1.0.0-alpha5"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha5"
If you're using Kotlin, you must also add or replace the annotationProcessor
with kapt
, which handles annotation on Kotlin.
kapt "android.arch.lifecycle:compiler:1.0.0-alpha5"
To enable kapt
, add the following in the module's build.gradle
root.
kapt { generateStubs = true }
3. The Lifecycle
Component
Every Android developer is familiar with the lifecycle concept. The system drives the lifecycle of Applications, Activities, Fragments, and so on, without the control of the developer. This concept is one of Android's paradigms and, until recently, it wasn't that easy to work with, since it wasn't possible to directly check for the current lifecycle status of a component. What we could do was to react to certain methods, like onCreate
and onDestroy
, triggered by lifecycle events.
This has all changed since the announcement of the Architecture Components package, which introduced a component called Lifecycle
. Now, some Android objects have a Lifecycle
attached to them, and this changes lots of things for the developers. It is possible to consult a Lifecycle
state at any given time, and it is also possible to react to Lifecycle
events using annotations. In fact, the backbone of the new Android Architecture Components is the Lifecycle
component.
All elements of the package android.arch.lifecycle
are important to the lifecycle concept, but two of them deserve more attention: LifecycleOwner
and LifecycleObserver
. They create the possibility of working with Lifecycle
, observing as well as reacting to events that occur on Activities, Fragments, Services and so on.
The LifecycleOwner
The LifecycleOwner
is a single method interface for classes that contain a Lifecycle
. It abstracts the possession of a Lifecycle
, allowing you to write components that can work with it. By the new standards, Activities and Fragments are LifecycleOwner
s. However, until the final version of the Architecture Components is launched, you must use some special classes: ActivityLifecycle
, FragmentLifecycle
, and LifecycleService
.
class MainActivity : LifecycleActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } }
There's no significant change in the implementation of these classes when compared to the standard Activities and Fragments. Once a class extends any of them, it will have a Lifecycle
attached, which can be fetched at any time with the method getLifecycle()
. Another interesting possibility is that we can check the current lifecycle state with getCurrentState
, which returns a Lifecycle.State
.
There are five different Lifecycle
states:
INITIALIZED
: for an object that has been called, but which isn't "active" yet. It is the equivalent to a state before theActivity.onCreate
method.CREATED
: for objects that were just created. It is called after theonCreate
method and also called just before theonStop
method.STARTED
: called after theonStart
and just before theonPause
method.RESUMED
: The active state or the resumed state for aLifecycleOwner
. Called after theonResume
method.DESTROYED
: for a destroyedLifecycleOwner
object. ThisLifecycle
won't dispatch more events. This event is reached right before theonDestroy
method.
The LifecycleObserver
One of the most interesting properties of the Lifecycle
is that it can easily be observed. LifecycleObserver
classes can observe LifecycleOwner
components, like Activities and Fragments. It receives LifecycleOwner.Event
s, and can react to them through the annotation @OnLifeCycleEvent( Lifecycle.Event )
.
class MainObserver : LifecycleObserver, AnkoLogger { @OnLifecycleEvent( Lifecycle.Event.ON_RESUME ) fun onResult() { info("onResult") } @OnLifecycleEvent( Lifecycle.Event.ON_STOP ) fun onStop() { info("onStop") } }
The methods annotated with @OnLifecycleEvent
don't need arguments, but if used, the first argument must be the LifecycleOwner
. When the annotation uses Lifecycle.Event.ON_ANY
, the method should expect two arguments: LifecycleOwner
and Lifecycle.Event
.
@OnLifecycleEvent( Lifecycle.Event.ON_ANY ) fun onEvent( owner: LifecycleOwner, event: Lifecycle.Event ) { info("onEvent: ownerState: ${owner.lifecycle.currentState}") info("onEvent: event: $event") }
To activate the @OnLifecycleEvent
annotation, the LifecycleObserver
must be observing a Lifecycle
, otherwise it won't receive the event. To make this work, call Lifecycle.addObserver(LifecycleOwner)
and the LifecycleOwner
will subsequently be able to react to Lifecycle.Event
. It is also possible to call Lifecycle.removeObsever(LifecycleObserver)
to remove an observer.
class MainActivity : LifecycleActivity(), AnkoLogger { @Inject lateinit var mainObserver: MainObserver override fun onCreate(savedInstanceState: Bundle?) { // ... // On Kotlin, instead of getLifecycle, // we can call lifecycle directly lifecycle.addObserver( mainObserver ) } override fun onDestroy() { // ... lifecycle.removeObserver( mainObserver ) } }
There are various interesting use-cases for LifecycleObserver
. For example, it could be used to create a Presenter
layer from the Model View Presenter architecture pattern. It could also be used to create listeners that can stop listening when the Lifecycle
is disabled.
4. The LiveModel
Component
Designed to work together with the UI layer, the ViewModel
component closes a gap that has existed in Android since the beginning: providing a way to elegantly manage and store data objects related to the view. The component maintains data integrity between configuration changes, can be shared between Activity and Fragments, and is an excellent tool to completely avoid memory leaks.
The ViewModel
is always created in close relation to a specific scope, either an Activity or Fragment. The scope is retained as long as the Activity or Fragment is alive. In practical terms, the ViewModel
reconnects with the view after configuration changes, maintaining itself until the main view is destroyed. According to the official documentation:
The purpose of the ViewModel
is to acquire and keep the information that is necessary for an Activity or a Fragment.
On top of all that, the ViewModel
facilitates the separation of concerns in the Android development process. By moving all the data-related operations to this component and letting it handle the logic, the application testability and maintainability is greatly increased. With ViewModel
, it's possible to easily adopt the Android Architecture proposed at the 2017 Google I/O. You can even use it to adopt more sophisticated architecture patterns, like MVP or MVVM.
Implementing a ViewModel
There are two ways to implement a ViewModel
. The standard one is to extend the class, providing a no-argument constructor. This is the easiest way, but it doesn't work well with Dependency Injection.
class MainViewModel : ViewModel() { init { // initialize some behavior } fun getData() : LiveData<String> { // get some data } override fun onCleared() { super.onCleared() // called before its destruction } }
To get a ViewModel
constructed with this technique from an Activity or Fragment, simply call ViewModelProviders.of(FragmentActivity).get(Class<T>)
. The last argument must contain the ViewModel
class. The same ViewModel
instance will be fetched by the view and will contain all the data for that view.
val viewModel: MainViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
Notice that, since the ViewModel
is taken from ViewModelProviders.of
method, its constructor cannot receive arguments. As a workaround, you can implement a ViewModelProvider.Factory
. Actually, this is the same technique we use to inject ViewModel
.
Injecting a ViewModel
When using DI, things get a little trickier. You'll need to implement a ViewModelProvider.Factory
. The following steps can be used to inject a ViewModel
using Dagger. The ViewModelFactory
is a utility class that provides a ViewModel
for a scope.
@Suppress("UNCHECKED_CAST") @Singleton class ViewModelFactory @Inject constructor( private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { var creator: Provider<out ViewModel>? = creators[modelClass] if (creator == null) { for ((key, value) in creators) { if (modelClass.isAssignableFrom(key)) { creator = value break } } } if (creator == null) { throw IllegalArgumentException("unknown model class " + modelClass) } try { return creator.get() as T } catch (e: Exception) { throw RuntimeException(e) } } }
Dagger also needs a @MapKey
defined for ViewModel
and a binder for each model and for the factory in the module.
// @MapKey @MustBeDocumented @Target( AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER ) @kotlin.annotation.Retention() @MapKey internal annotation class ViewModelKey( val value: KClass<out ViewModel>) // ViewModel Module @Module abstract class ViewModelsModule { // Bind each ViewModel @Binds @IntoMap @ViewModelKey( MainViewModel::class ) abstract fun bindMainViewModel( mainViewModel: MainViewModel ) : ViewModel // ViewModel factory binding @Binds abstract fun bindViewModelFactory( factory: ViewModelFactory ) : ViewModelProvider.Factory }
After that, follow the standard Dagger procedures and you'll be able to create a ViewModel
capable of injecting arguments on its constructor. To create a new instance, get the ViewModelFactory
and take the desired ViewModel
from it.
// Get the ViewModel factory @Inject lateinit var viewModelFactory: ViewModelProvider.Factory // Get ViewModel val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(MainViewModel::class.java)
In our sample project, you can take a close look at DI using Dagger. I've also provided you with a folder of examples in the tutorial GitHub repo with snippets showing how to configure ViewModels
on the Dagger system using Kotlin.
class MainViewModel @Inject constructor( private val repository: MainRepository ) : ViewModel(), AnkoLogger { // ... code goes here }
Conclusion
So far, our journey through the new Android Architecture Components has been very productive. However, we still have some ground to cover. In the next tutorial, we’ll talk about the awesome LiveData
component, investigating its basic and advanced features, and applying these concepts to our sample application.
See you soon! And in the meantime, check out some of our other posts on Android app development!
- Android SDKAndroid O: Phone Number Verification With SMS Tokens
- Android ThingsAndroid Things: Creating a Cloud-Connected Doorman
- Android SDKCreate an Intelligent App With Google Cloud Speech and Natural Language APIs