Android Architecture Components With Kotlin - Lifecycle
17 Jul 2017Google recently released its official guide to the Android app architecture with a bunch of libraries called Architecture Components. This looks very promising since Android has been missing a standard way of implementing some kind of clean architecture. There were many unofficial ways to implement MVP and MVVM patterns, from which I had a chance to use a couple, including Mosby.
The problem was lack of standards. When I switched a project I had to learn completely new MVP/MVC/MVVM implementation which did exactly the same thing I was used to, just in a different way. I always thought it would be nice if Google provided some “official” support for app architecture which the industry could adopt.
In this post, I’m going to explore the ViewModel, LifecycleOwner, and LiveData from the Architecture Components Library. All the code here is of course Kotlin.
The code for this tutorial is available here. The sample app requires Android Studio 3.0, at the time of writing this post I used Android Studio 3.0 Canary 5. If you want to use Canary version of Android Studio along your stable version on the same machine, check out this video. I will update the GitHub repository to more stable versions of Android Studio 3.0 when available.
As an example, I created a simple app which contains Activity and two Fragments. The app is showing two Fragments connected to shared Activity. The first Fragment is displaying simple countdown timer, the second Fragment has all the controls:
When you tap “START” the app starts the counter:
The state of the counter is preserved during orientation change, when the shared Activity is being recreated:
You can also stop the timer or log the timer state.
App’s architecture explained
First, we need to import the required library. To do this we add the dependency to the apps build.gradle file in dependencies section:
// Android Architecture Components
compile "android.arch.lifecycle:extensions:$arch_version"
where '$arch_version'
is the library’s version. Google is currently providing Architecture Components as a separate library but after reaching v1.0 it will be part of the support libraries.
Our shared Activity extends LifecycleActivity() from the library.
package com.kotlinblog.arch | |
import android.arch.lifecycle.LifecycleActivity | |
import android.os.Bundle | |
class SharedActivity : LifecycleActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
} | |
} |
Our app’s business logic is located in SharedViewModel class, which extends ViewModel class. Its purpose is to “store and manage UI-related data so that the data survives configuration changes such as screen rotations”. The business logic here is a simple countdown timer. We store the formatted time String as MutableLiveData, an Architecture Components holder which stores data that can be observed. The difference between LiveData is that it exposes setters. Note that we also use a TimerStateModel
object (custom Kotlin Data class) which also survives orientation change:
private val mFormattedTime = MutableLiveData<String>() | |
private val mTimerState = TimerStateModel(false, null) | |
// Getter for mFormattedTime LiveData, cast to immutable value | |
val formattedTime: LiveData<String> get() = mFormattedTime |
In FirstFragment we make the formattedTime observed. First we create timeObserver
which updates our tvTimer
TextView every time the mFormattedTime
in our SharedViewModel
is getting changed. We are obtaining instance of our SharedViewModel
(see code line 10 below) and then we subscribe to the observer (code line 13). The activity
cast as LifecycleOwner
which we pass to the observe
method is Kotlin’s convenience method to get our SharedActivity
instance. We are accessing formattedTime
via our public getter.
fun observe() { | |
// Creating an observer | |
val timeObserver = Observer<String> { formattedTime -> | |
if (formattedTime != null) { | |
tvTimer.text = formattedTime | |
} | |
} | |
// Getting instance of the SharedViewModel | |
val sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java) | |
// Subscribing to the observer | |
sharedViewModel.formattedTime.observe(activity as LifecycleOwner, timeObserver) | |
} |
In the SecondFragment in onViewCreated
method first we obtain an instance of our SharedViewModel
and then we set all the appropriate onClickListeners (code lines 7-9).
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { | |
super.onViewCreated(view, savedInstanceState) | |
// Getting instance of the SharedViewModel | |
val sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java) | |
btnStart.setOnClickListener { sharedViewModel.startTimer() } | |
btnStop.setOnClickListener { sharedViewModel.stopTimer() } | |
btnToast.setOnClickListener { sharedViewModel.showLog() } | |
} |
In this simple example we delegated all of the business logic to a ViewModel
. Our fragments are only responsible for observing the data changes (using convenience of LiveData) and responding for user input. Everything is managed by the Architecture Components library so there is very little boilerplate. Also, it reduces the possibility of shooting ourselves in the foot by mismanaging app state, which in my experience is one of the most common bugs every Android developer experiences.
If you want to do some further reading about Architecture Components I recommend those excellent articles:
Exploring the new Android Architecture Components library by Joe Birch
ANDROID ARCHITECTURE COMPONENTS – LOOKING AT VIEWMODELS – PART 2 and ANDROID ARCHITECTURE COMPONENTS – LOOKING AT LIFECYCLES – PART 3 by Rebecca Franks