At the last Google I/O, the Android team released a set of powerful Android Architecture Components. They call it:
A collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.
If you haven’t learnt about them, you are strongly advised to check out our awesome series here on Envato Tuts+ about Android Architecture Components by Tin Megali. Make sure you go dive in!
-
Android SDKIntroduction to Android Architecture Components
In this tutorial, I’ll show you how to use the LiveData
components from the Android Architectural Components to create an event bus. An event bus can be used to effectively communicate between Android components or between layers of your application—for example, communicating to an Activity
from an IntentService
that a file has finished downloading.
We’ll build a very simple app that triggers an IntentService
to do some work—from an Activity
. Our IntentService
will then communicate back to the Activity
when the work is completed. Our communication channel will be from the LiveData
library.
Prerequisites
To be able to follow this tutorial, you’ll need:
- Android Studio 3.0 or higher
- Kotlin plugin 1.1.51 or higher
- a basic understanding of the Android Architectural Components (especially the
LiveData
component) - a basic understanding of an event bus
You can also learn all the ins and outs of the Kotlin language in my Kotlin From Scratch series.
-
Kotlin From Scratch: Variables, Basic Types, and Arrays
-
Kotlin From Scratch: Classes and Objects
1. Create an Android Studio Project
Fire up Android Studio 3 and create a new project with an empty activity called MainActivity
.
2. Add the Lifecycle Components
After creating a new project, specify the LifeCycle
and the LiveData
artifacts in your app module’s build.gradle
. Note that as of this writing, the new architectural components are now in a stable version. So this means you can start using them in production apps.
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" implementation 'com.android.support:appcompat-v7:26.1.0' implementation "android.arch.lifecycle:runtime:1.0.3" implementation "android.arch.lifecycle:extensions:1.0.0" }
These artifacts are available at Google’s Maven repository.
allprojects { repositories { google() jcenter() } }
By adding the dependencies, we have taught gradle how to find the library. Make sure you remember to sync your project after adding them.
3. Create the LifecycleOwner
Activity Subclass
Here our MainActivity
implements the LifecycleOwner
interface.
import android.arch.lifecycle.Lifecycle import android.arch.lifecycle.LifecycleOwner import android.arch.lifecycle.LifecycleRegistry import android.arch.lifecycle.Observer import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.View import android.widget.Button import android.widget.TextView class MainActivity : AppCompatActivity(), LifecycleOwner { private val registry = LifecycleRegistry(this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) } override fun getLifecycle() : Lifecycle = registry override fun onStart() { super.onStart() registry.handleLifecycleEvent(Lifecycle.Event.ON_START) } override fun onResume() { super.onResume() registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } override fun onPause() { super.onPause() registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) } override fun onStop() { super.onStop() registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) } override fun onDestroy() { super.onDestroy() registry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } }
Our activity simply handles the standard activity lifecycle events. Inside each of the lifecycle events, it calls the registry.handleLifecycleEvent()
, passing the corresponding event as a parameter.
4. Create the Layout
We just have a Button
that triggers the service. A TextView
(invisible by default) shows the text "Work completed!"
when the service communicates to our MainActivity
.
5. Initialize the Widgets
We declared our doWorkButton
and resultTextView
properties inside the MainActivity
class with the lateinit
modifier. We then initialize them inside the onCreate()
method. Anytime the doWorkButton
is clicked, we disable it (to prevent clicking the button more than once) and start our MyIntentService
(we’ll get to that shortly).
class MainActivity : AppCompatActivity(), LifecycleOwner { private lateinit var doWorkButton: Button private lateinit var resultTextView: TextView override fun onCreate(savedInstanceState: Bundle?) { // ... doWorkButton = findViewById(R.id.btn_download) doWorkButton.setOnClickListener { doWorkButton.isEnabled = false resultTextView.visibility = View.INVISIBLE val serviceIntent = Intent(this, MyIntentService::class.java) startService(serviceIntent) } resultTextView = findViewById(R.id.tv_result) } // ... }
6. Create the Custom Event Class
We just create a simple event message class that we want to pass around on the event bus (or LiveData
).
data class CustomEvent (val eventProp: String)
You can add more properties to this class if you want.
7. Service Implementation
We implemented an IntentService called MyIntentService
. Remember that IntentService
lives outside the activity scope and has a background thread, so it is recommended to perform time-consuming tasks such as downloading or fetching remote data via an API inside it.
However, note that in Android 8.0 if you don’t make your IntentService
a foreground service by using startForeground()
, the Android system will not allow your service to run more than 1 minute—or else it will be stopped immediately. This mechanism is to efficiently manage system resources such as battery life. If your app is targeting Android 8.0, you are advised to use the JobIntentService instead.
import android.app.IntentService import android.arch.lifecycle.MutableLiveData import android.content.Intent import android.os.SystemClock class MyIntentService: IntentService("MyIntentService") { companion object { var BUS = MutableLiveData() } override fun onHandleIntent(intent: Intent?) { // simulate work SystemClock.sleep(3000) // assuming work is done val event = CustomEvent("value") if (BUS.hasActiveObservers()) { BUS.postValue(event) } else { // show notification } } }
We create a nameless companion object whose companion class is MyIntentService
. This companion object has a property called BUS
, which is an instance of MutableLiveData
. Remember that companion objects are singletons, so this means that only a single instance of BUS
exists. We also passed our CustomEvent
as a type argument to the generic MutableLiveData
class.
Remember that the MutableLiveData
class is a subclass of LiveData
—and has a method called postValue()
that can be called from a background thread.
public class MutableLiveDataextends LiveData { @Override public void postValue(T value) { super.postValue(value); } @Override public void setValue(T value) { super.setValue(value); } }
Inside onHandleIntent()
, we have our business logic. Remember that this method is called on a background thread (one of the major differences between an IntentService
and a normal Service
). The IntentService
ends immediately by itself when the onHandleIntent()
method finishes its job.
In our own case, we are simulating work being done (this work can be a file download or communicating with a remote API) by sleeping the current thread for 30 seconds. We then checked if our BUS
has any active observers using the hasActiveObservers()
method. If there are any, notify and pass our event message to them by using the method postValue()
, or else we can simply show a notification (this was not coded in the example above for brevity’s sake).
Remember to include the service in your manifest file.
8. Observer Implementation
We need at least one observer for our mechanism to be useful. So inside the MainActivity
class, we are going to subscribe an anonymous observer.
class MainActivity : AppCompatActivity(), LifecycleOwner { // ... override fun onCreate(savedInstanceState: Bundle?) { // ... MyIntentService.BUS.observe( this, Observer { event -> resultTextView.visibility = View.VISIBLE downloadButton.isEnabled = true Log.d("MainActivity", event?.eventProp) }) } // ... }
Inside the onCreate()
of MainActivity
, we got the event bus BUS
from MyIntentService
. Then we registered an observer for the event bus (i.e. LiveData
) using the observe()
method. Next, we registered and inlined an anonymous observer, using the MainActivity
as LifecycleOwner
. This anonymous observer gets notified when any of the following happens:
- There is already data available in the
LiveData
when it subscribes. - The data inside the
LiveData
gets modified.
When either of these occurs, we get the event
data (from the LiveData
) on the main application thread as input to the lambda. We then do the following inside the lambda’s body:
- Make the
resultTextView
visible. - Enable the
doWorkButton
. - Log our custom event property
eventProp
value to Logcat.
Remember the following about LiveData
:
- When a new observer is attached to our
LiveData
after a configuration change,LiveData
will send the last data it received to the observer—even without us explicitly telling it to do so. In other words, it does this automatically. - When the
LifecycleOwner
is destroyed, the observer will automatically be unsubscribed. - Finally,
LiveData
is an observable that is lifecycle-aware. According to the docs:
LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.
9. Testing the App
Finally, you can run the app! Click the Do Work button and after 30 seconds, you’ll see the result.
You can get the complete source code from our GitHub repo.
Conclusion
In this tutorial, you learned how to easily use the LiveData
components from the Android Architectural Components to create an event bus—so as to effectively communicate with components of your app.
I assume you’re aware of other libraries you can use for the same purpose, such as Android LocalBroadcastManager or the popular greenrobot EventBus to implement an event bus in your Android application. You can see that using the LiveData
instead is preferable to them—because you avoid writing boilerplate or verbose code, and LiveData
provides you with better flexibility.
To learn more about coding for Android, check out some of our other courses and tutorials here on Envato Tuts+!
-
AndroidCommunication Within an Android App With EventBus
-
AndroidHow to Get Started With Push Notifications On Android
-
Google MapsGetting Started With Google Maps for Android: Basics
-
Android SDKConcurrency in RxJava 2
Powered by WPeMatico