Event-based systems on Android — feat. RxJava and Kotlin

It was the best of times. It was the worst of times. So begins our tale of two classes.

Sending out an S.O.S…

Imagine a software system that is divided into two components: it is common parlance to design software systems to be modular; that is, a given feature should contain within itself the full specification of what it needs to operate. It may have external contingencies, but its internal logic is agnostic as to what the sources of those contingencies are.

In such a system, it is sometimes necessary to cross some sort of boundary, whether a synthetically designed boundary within the software system itself, or an actual hardware boundary where some conversion of information and energy needs to happen, such as when reaching out to a server on a computer somewhere.

In this article, I want to focus on boundaries within a given software system. In Android, there are many such boundaries that we are forced to deal with on a constant basis. For instance, there are the Activity and Fragment interfaces. Information does not readily pass between Activity and Fragment but through a particular transfer mechanism called a Bundle. Bundles are the interface provided for sending information across the Activity/Fragment divide, so that it will become available at initialization time.

Information transfer is often more like a private road than an open highway. We need to design systems which support stable inter-module communication, despite such channels being restricted. One way to do this is by means of events. RxJava provides natural mechanisms enabling communication across architectural boundaries by means of “observable” event streams.

So join me in this series of “events”, as we engage in this fun “activity” and look at some awesome code “fragments”…

Creating the events

A common use case is to have an activity which houses a fragment. For simplicity, we are going to have a fragment which, when a button is pressed, triggers an event on the Activity. The Activity then proceeds to handle the logic pertaining to dismissing the Fragment.

Kotlin provides a neat feature known as a Sealed Class. And when I say neat, I mean really neat. Sealed classes are like enums. They let you fully specify possible instances of a class. Then you can safely iterate over the possibilities without fearing that you missed any.

We want a sealed class that will enumerate the types of events we expect to use. To start with, let’s say we have two events, Show and Dismiss, which will — unsurprisingly — be responsible for showing and dismissing the dialog, respectively:

// AppEvent.kt
sealed class AppEvent {
object Show: AppEvent()
object Dismiss: AppEvent()
}

Since our events don’t have constructors, we can use object syntax to declare a single instance representing that event. By doing this, references to AppEvent.Show will always point to the same instance (***we can therefore use === to perform referential equality checks).

Bringing in RxJava

So we have our events, but how do we start broadcasting and subscribing to them from our components?

How about with RxJava? We should just need one library to start using RxJava on Android:

// rxAndroid
implementation "io.reactivex.rxjava2:rxandroid:2.1.0"

We will accomplish this through the use of an event stream called a Flowable, and it’s foil the PublishProcessor. The former is what we will subscribe to to listen to events; the latter we will use to publish events on that same stream. As we will see, it is possible to directly cast a PublishProcessor to a Flowable. That way, we know for sure we are publishing and subscribing to the same event stream. Let’s add on to AppEvent.kt:

val appEventProcessor: PublishProcessor<AppEvent> = PublishProcessor.create()
val appEventFlowable = appEventProcessor as Flowable<AppEvent>

In Kotlin, you can do naked variable declarations like this from any file. These declarations will effectively function as global variables, accessible from anywhere within your program.

***Important note: it is also possible to do this with Dagger 2. Actually my initial intention had been to do it that way, providing the PublishProcessor and Flowable from a module and injecting them. That is probably the more elegant and extensible approach. But it is a lot easier to explain this way.

Subscribe to AppEvents from Activity

Now we’re ready to setup our subscription from the host activity. In my experience, it is common to listen for events from Activities, while publishing events from Fragments. That way, the Activity can manage all the transitions and UI updates safely, orchestrating everything that should happen below it, from above.

I usually set up a subscription with a method on the Activity, like so:

// MainActivity.kt
private fun createAppEventsSubscription(): Disposable =
appEventFlowable
.doOnNext {
when (it) {
AppEvent.Show -> { /* do something */ }
AppEvent.Dismiss -> { /* do something */ }
}
}
.subscribe()

***Edit
As noted in the comments by Areeb Jamal, I have fallen victim to the pitfall of “using side effects.” That is, I should perform the required updates in subscribe, and also pass an error handler, rather than using doOnNext with a raw subscribe() tacked on the end, leaving myself vulnerable to unexpected errors. I’m leaving it this way because I think it’s more readable, but readers should be advised that this is technically an anti-pattern and should generally be avoided. 
***

Notice that we are not required to put an else clause in the when block. That is because the compiler is able to infer we have exhausted all possibilities by using a Sealed Class.

I add this subscription to a CompositeDisposable in my Activity’s onCreate() method, and make sure to clean up my subscriptions in onStop(). This is basically to disable subscriptions when the Activity is not within the “visible” section of its lifecycle:

// MainActivity.kt
var compositeDisposable = CompositeDisposable()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

compositeDisposable.add(createAppEventsSubscription())
}
override fun onStop() {
super.onStop()
compositeDisposable.clear()
compositeDisposable = CompositeDisposable()
}

The Fragment

Let’s create a simple Fragment with a button. Clicking the button will publish a Dismiss event to our Processor, and we will respond accordingly in the Activity.

Here’s our layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
android:id="@+id/dismiss_button"
android:text="DISMISS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</android.support.constraint.ConstraintLayout>

As you can see, it’s literally just a centered button that says DISMISS on it.

Now the Fragment:

// MainFragment.kt
class MainFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_main, container).apply {
dismiss_button.setOnClickListener {
appEventProcessor.onNext(AppEvent.Dismiss)
}
}
}

We do a lot of cool Kotlin stuff here, utilizing apply to return the receiver after inflating the view, as well as synthetic view references to assign a click listener. More importantly, we trigger AppEvent.Dismiss on our global processor whenever the button is clicked. Notice the Fragment is not handling any other logic itself. That part will be managed by the subscriber of the event, our Activity.

Completing the circuit

Back in MainActivity.kt, we need to respond to Show and Dismiss events by showing and dismissing a MainFragment. Let’s also keep a reference to the fragment so we can easily access it:

// MainActivity.kt
var mainFragment: MainFragment? = null
private fun createAppEventsSubscription(): Disposable =
appEventFlowable
.doOnNext { Log.d("AppEvents", "$it") }
.doOnNext {
when (it) {
AppEvent.Show -> {
if (mainFragment == null) {
mainFragment = MainFragment().apply {
supportFragmentManager
.beginTransaction()
.add(android.R.id.content, this)
.commit()
}
}
}
AppEvent.Dismiss -> {
mainFragment?.let {
supportFragmentManager
.beginTransaction()
.remove(it)
.commit()
}

mainFragment = null
}
}
}
.subscribe()

Let’s also automatically trigger a Show event in OnStart():

// MainActivity.kt
override fun onStart() {
super.onStart()
appEventProcessor.onNext(AppEvent.Show)
}

And that’s it! Running the app should show you sequence of two states: one when the app loads, showing the Fragment, and one revealing the original Activity after clicking the dismiss button:

So that was a whirlwind toward of event-based architecture as it relates to Android. This is one angle, using RxJava, which I highly prefer as it makes for a very understandable and extensible architecture. It allows you to respond to changes right within the components to whom those changes affect, rather than delegating responsibilities all over the place in a tangled mess.

If you read this far, thanks so much, hope you found something interesting and of value. See you next time!