New Architecture Components in Android.
Usually, when we create complex Android Application we have a lot of different components.
Most applications have UI that represented by Activities and Fragments, server-side communications, database. And all of them have to communicate to each other all the time.
We create Activity that needs some information from a server. This information has to be cached in a database. And then if we haven’t been careful enough we can face a lot of troubles. Most common example: Activity changed orientation, was recreated and we made the same request again, instead of using previous data. Or Async task tried to use an old version of Activity and our app crashed. Or for example, we have to share the same complex and big data between different activities and fragments and we have to create our own way to do that.
Of course, now days there are lots of different tools that help us to solve those problems. You can use eventbus to deliver events and RXJava for some task. There are a lot of different ORM to cache data and so on. But there are no standards. Every developer creates his own way to handle those problems.
And finally, in I/O 2017 Google present something that we were waiting for a very long time: New Architecture Components.
- Livecycles
- LiveData
- ViewModel
- Room Persistence Library
In this article, I will talk about first three components.
To add these components to your project:
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
For Livecycles, LiveData, ViewModel:
compile "android.arch.lifecycle:runtime:1.0.0"
compile "android.arch.lifecycle:extensions:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
Notice, this library still in alpha, so be careful. I already tried them in some of my projects. And here I will show some examples how you can use them.
We always depend on the lifecycle of Activity’s or Fragment’s lifecycle. There are lots of managers and components that have to be created and destroyed in time. Location services, Camera etc, etc. And in every activity we use them we need to create and destroy, subscribe and unsubscribe. It’s ok if you use them only in one Activity. But this code can not be reused. And I am pretty sure we don’t want to create a huge sequence of extended activities to support same features. Right?
1 Lifecycle
The first component is Lifecycle. There are two main classes you need.
- LifecycleRegistryOwner
- LifecycleObserver
LifecycleRegistryOwner is an interface that extended LivecycleOwner interface and returns LifecycleRegisty which is an implementation of Lifecycle that can handle multiple observers.
With new update AppCompatActivity from v7 library and Fragment from v4 both are implemented LifecycleRegistryOwner.
Now you can create a class that will depend on Lifecycle of Activity or Fragment, using LifecycleObserver
.
Let’s say we have a Service and we want to bind this service to your Activity on start and unbind on stop.
class MyBinderClass: LifecycleObserver {
private val TAG = MyBinderClass::class.java.name
// Class that implement ServiceConnection interface and
// logick for bind and unbind.
// You have to implement your own.
private var connection: MyServiceConnection? = MyServiceConnection()
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStartEvent() {
Log.d(TAG, "Lifecycle on start")
unbind()
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStopEvent() {
Log.d(TAG, "Lifecycle on stop")
unbind()
}
fun bind() {
connection?.doBindService()
Log.d(TAG, "do bind service")
}
fun unbind() {
connection?.doUnbindService()
connection = null
value = null
Log.d(TAG, "do unbind service")
}
}
Our class has to implement LifecycleObserver so we can use @OnLifecycleEvent
annotation. There are 7 types of event. You can find them in Lifecycle.Event
enum. Now we can add our observer to LifecycleActivity or LifecycleFragment.
val myBinder = MyBinderClass()
lifecycle.addObserver(myBinder)
Now every time when Lifecycle Owner (Activity or Fragment) will stop or start our annotated methods will be executed.
Note:
ON_CREATE
,ON_START
,ON_RESUME
events in this class are dispatched after theLifecycleOwner
's related method returns.ON_PAUSE
,ON_STOP
,ON_DESTROY
events in this class are dispatched before theLifecycleOwner
's related method is called.
LifecycleObserver can be very useful, but in most cases like this, we can use LiveData class.
2 Live Data
LiveData is a data holder class that keeps a value and allows this value to be observed. Unlike a regular observable, LiveData respects the lifecycle of app components, such that the Observer can specify a Lifecycle in which it should observe.
This is the actual example from Android Developer site I’m using in my project.
class LocationLiveData(context: Context) : LiveData<Location>(),
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private var googleApiClient: GoogleApiClient? = null
override fun onActive() {
googleApiClient?.connect()
}
override fun onInactive() {
if (googleApiClient?.isConnected ?: false) {
LocationServices.FusedLocationApi.removeLocationUpdates(
googleApiClient, this)
}
googleApiClient?.disconnect()
}
override fun onConnected(p0: Bundle?) {
// Try to immediately find a location
val lastLocation = LocationServices.FusedLocationApi
.getLastLocation(googleApiClient)
if (lastLocation != null) {
value = lastLocation
}
// Request updates if there’s someone observing
if (hasActiveObservers()) {
LocationServices.FusedLocationApi.requestLocationUpdates(
googleApiClient, LocationRequest(), this)
}
}
override fun onConnectionSuspended(p0: Int) {
}
override fun onConnectionFailed(p0: ConnectionResult?) {
}
override fun onLocationChanged(p0: Location?) {
value = p0
}
init {
googleApiClient = GoogleApiClient.Builder(context, this, this)
.addApi(LocationServices.API)
.build()
}
}
to Observe Location Data:
val locationLiveData = LocationLiveData(activity) locationLiveData?.observe(this, Observer {
data -> //Do something here with data
})
The basic idea here is that we connect onActive method and disconnect on inactive. LiveData object depends on Lifecycle.
So when Activity/Fragment go to background LiveData go to inactive state automatically.
You can add observers to LiveData. The observer will be called every time when the value is changed.
If live data doesn’t have observers it will stay in inactive state.
If Lifecycle is not STATED or RESUMED LiveData will stay in inactive state and observers won’t get an update.
Ok. Now we can handle Lifecycle very easily. Now lets separate Data from UI. Meet next component.
3 View Model
The ViewModel class is designed to store and manage UI-related data so that the data survives configuration changes such as screen rotations.
For example, you have a social app and you want to download feed. You can create FeedViewModel.
class FeedViewModel : ViewModel() {
private var feed: MutableLiveData<List<Post>>? =
MutableLiveData()
fun getFeed(): LiveData<List<Post>> {
if (feed == null) {
feed = MutableLiveData()
loadFeed()
}
return feed!!
}
private fun loadFeed() {
AsyncTask.execute {
val feedData = loadFeedFromTheServer()
feed?.postValue(feedData)
}
}
}
After you call getFeed, FeedViewModel will create LiveData. You can add observers to this LiveData and when data will be loaded, an observer will be executed. Notice that if we want to change a value of LiveData in background thread we need to use the postValue
method.
To get this data use:
val viewModel = ViewModelProviders.of(this).get(
FeedViewModel::class.java)
As you can see we not creating new ViewModel every time we need it. So this view model is independent of Activity/Fragment. We can reuse it without worrying about Activity Recreation and so on. The same way we can share data between different screens.
You can find some working examples including Room Persistence Library on my GitHub.