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

We should just need one library to start using RxJava on Android:// rxAndroidimplementation "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 ActivityNow 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.

ktprivate fun createAppEventsSubscription(): Disposable = appEventFlowable .

doOnNext { when (it) { AppEvent.

Show -> { /* do something */ } AppEvent.

Dismiss -> { /* do something */ } } } .

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

ktvar 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 FragmentLet’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.

ktclass 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 circuitBack 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.

ktvar mainFragment: MainFragment?.= nullprivate 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.

ktoverride 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!.. More details

Leave a Reply