Advertisement
  1. Code
  2. Coding Fundamentals
  3. Design Patterns

Implementing an Event Bus With LiveData

Scroll to top
Final product imageFinal product imageFinal product image
What You'll Be Creating

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! 

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:

You can also learn all the ins and outs of the Kotlin language in my Kotlin From Scratch series.

1. Create an Android Studio Project

Fire up Android Studio 3 and create a new project with an empty activity called MainActivity

Android Studio new project screenAndroid Studio new project screenAndroid Studio new project screen

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. 

1
dependencies {
2
    implementation fileTree(dir: 'libs', include: ['*.jar'])
3
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
4
    implementation 'com.android.support:appcompat-v7:26.1.0'
5
    
6
    implementation "android.arch.lifecycle:runtime:1.0.3"
7
    implementation "android.arch.lifecycle:extensions:1.0.0"
8
}

These artifacts are available at Google’s Maven repository. 

1
allprojects {
2
    repositories {
3
        google()
4
        jcenter()
5
    }
6
}

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. 

1
import android.arch.lifecycle.Lifecycle
2
import android.arch.lifecycle.LifecycleOwner
3
import android.arch.lifecycle.LifecycleRegistry
4
import android.arch.lifecycle.Observer
5
import android.content.Intent
6
import android.os.Bundle
7
import android.support.v7.app.AppCompatActivity
8
import android.view.View
9
import android.widget.Button
10
import android.widget.TextView
11
12
class MainActivity : AppCompatActivity(), LifecycleOwner {
13
    private val registry = LifecycleRegistry(this)
14
15
    override fun onCreate(savedInstanceState: Bundle?) {
16
        super.onCreate(savedInstanceState)
17
        setContentView(R.layout.activity_main)
18
        registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
19
    }
20
21
    override fun getLifecycle() : Lifecycle = registry
22
23
    override fun onStart() {
24
        super.onStart()
25
        registry.handleLifecycleEvent(Lifecycle.Event.ON_START)
26
    }
27
28
    override fun onResume() {
29
        super.onResume()
30
        registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
31
    }
32
33
    override fun onPause() {
34
        super.onPause()
35
        registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
36
    }
37
38
    override fun onStop() {
39
        super.onStop()
40
        registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
41
    }
42
43
    override fun onDestroy() {
44
        super.onDestroy()
45
        registry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
46
    }
47
}

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

1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout
3
        xmlns:android="https://schemas.android.com/apk/res/android"
4
        xmlns:tools="http://schemas.android.com/tools"
5
        android:layout_width="match_parent"
6
        android:layout_height="match_parent"
7
        android:orientation="vertical"
8
        android:gravity="center_horizontal|center_vertical">
9
10
    <Button
11
            android:id="@+id/btn_download"
12
            android:layout_width="wrap_content"
13
            android:layout_height="wrap_content"
14
            android:text="Do work"/>
15
16
    <TextView
17
            android:id="@+id/tv_result"
18
            android:layout_width="wrap_content"
19
            android:layout_height="17dp"
20
            android:text="Work completed!"
21
            android:visibility="invisible"/>
22
</LinearLayout>

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). 

1
class MainActivity : AppCompatActivity(), LifecycleOwner {
2
3
    private lateinit var doWorkButton: Button
4
    private lateinit var resultTextView: TextView
5
6
    override fun onCreate(savedInstanceState: Bundle?) {
7
        // ...

8
9
        doWorkButton = findViewById(R.id.btn_download)
10
        doWorkButton.setOnClickListener {
11
            doWorkButton.isEnabled = false
12
            resultTextView.visibility = View.INVISIBLE
13
            val serviceIntent = Intent(this, MyIntentService::class.java)
14
            startService(serviceIntent)
15
        }
16
17
        resultTextView = findViewById(R.id.tv_result)
18
    }
19
    // ...

20
}

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). 

1
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. 

1
import android.app.IntentService
2
import android.arch.lifecycle.MutableLiveData
3
import android.content.Intent
4
import android.os.SystemClock
5
6
class MyIntentService: IntentService("MyIntentService") {
7
8
    companion object {
9
        var BUS = MutableLiveData<CustomEvent>()
10
    }
11
12
    override fun onHandleIntent(intent: Intent?) {
13
14
        // simulate work

15
        SystemClock.sleep(3000)
16
17
        // assuming work is done

18
        val event = CustomEvent("value")
19
20
        if (BUS.hasActiveObservers()) {
21
            BUS.postValue(event)
22
        } else {
23
           // show notification

24
        }
25
    }
26
}

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. 

1
public class MutableLiveData<T> extends LiveData<T> {
2
    @Override
3
    public void postValue(T value) {
4
        super.postValue(value);
5
    }
6
7
    @Override
8
    public void setValue(T value) {
9
        super.setValue(value);
10
    }
11
}

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.

1
<service android:name="MyIntentService"/>

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. 

1
class MainActivity : AppCompatActivity(), LifecycleOwner {
2
    
3
    // ...

4
    override fun onCreate(savedInstanceState: Bundle?) {
5
      
6
        // ... 

7
        MyIntentService.BUS.observe(
8
                this,
9
                Observer { event ->
10
                    resultTextView.visibility = View.VISIBLE
11
                    downloadButton.isEnabled = true
12
                    Log.d("MainActivity", event?.eventProp)
13
                })
14
    }
15
    // ...

16
}

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. 

Tutorial project result Tutorial project result Tutorial project 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+!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.