Android: Reactive View Part III - Rx Subjects
This post is the 3rd part of the series of blog posts I'm writing about making Android view layer Reactive.
- Android Reactive View Part I
- Android Reactive View Part II
- Android: Reactive View Part III - Rx Subjects ← This Post Is Here!
In Part I and Part II, I wrote about how buttons are made reactive. By using RxBinding's extension function, it's pretty easy:
val button: Button
val observable: Observable<Unit> = button.clicks()
The implementation of the .clicks() methods can be found here (ViewClickObservable.kt). If you look into the implementation, this line here shows that the setOnClickListener() of the View to re-emit the click event:
override fun onClick(v: View) {
if (!isDisposed) {
observer.onNext(Unit)
}
}
Without using the RxBinding, there are a couple of ways to make something Reactive, through the various RxJava Observable creation methods.
When none of this methods fit, a Subject can be used as a bridge to make something Reactive.
In this post, let's look at these 2 cases:
- how
RecyclerViewcan be made reactive - how
startActivityForResult()andonActivityResult()can be made reactive
Subjects
First let's take a look at Subject, the definition says that it is:
a sort of bridge or proxy that is available in some implementations of ReactiveX that acts both as an observer and as an Observable. Because it is an observer, it can subscribe to one or more Observables, and because it is an Observable, it can pass through the items it observes by reemitting them, and it can also emit new items.
I often find definition hard to understand. Here's my attempt to explain Subject. It is something that can receive events (blue) and also emitting events (red).

What it means is that if you have a few Observables, for example:
button1.clicks()button2.clicks()button3.clicks()
A subject can be used to receive from them.
val button1Obs = button1.clicks().map { 1 }
val button2Obs = button2.clicks().map { 2 }
val button3Obs = button3.clicks().map { 3 }
val subject = PublishSubject.create<String>()
Observable.merge(button1Obs, button1Obs, button1Obs)
.subscribe(subject)
After receiving signals from the buttons, subject can re-emit them. Since subject is emitting, it can be subscribed .
Note: It depends on which type of Subject it is, the timing of subscription determines what the
Observerwill receive
Continuing from the example above, the subject can be subscribed() to show a log message like below. 👇
subject.subscribe { buttonNumber ->
Log.d("debug", "button ${buttonNumber} is clicked!")
}
// output:
// button 1 is clicked!
// button 2 is clicked!
// button 3 is clicked!
Alternative
Another way to re-emit with Subject is by using the onNext() method. Here's the syntax, check the difference in the syntax:
val button1Obs = button1.clicks().map { 1 }
val button2Obs = button2.clicks().map { 2 }
val button3Obs = button3.clicks().map { 3 }
val subject = PublishSubject.create<String>()
Observable.merge(button1Obs, button1Obs, button1Obs)
- .subscribe(subject)
+ .subscribe { subject.onNext(it) }
Types of Subject
There are a few types of Subject:
PublishSubjectBehaviourSubjectReplaySubject
is out of the scope of this post, feel free to head over to these post series by David Karnok for a deeper dive.
Using Subject for RecyclerView
For RecyclerView, the use of Subject can be used in a similar way to receives click events and re-emit them.

Let's say we have a MainActivity, containing a RecyclerView and an Adapter:

The code for MainActivity.kt and MainAdapter.kt:
val adapter = MainAdapter()
val list = listOf("row1", "row2", "row3")
fun onCreate() {
setContentView(R.layout.activity_reactive_views)
// setup RecyclerView
recyclerView.adapter = adapter
adapter.submitList(list)
}
class MainAdapter : ListAdapter<String, RecyclerView.ViewHolder>(...) {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val textToDisplay = getItem(position)
(holder as MainViewHolder).bind(textToDisplay)
}
class MainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(textToDisplay: String) {
itemView.apply {
rowTextView.text = textToDisplay
}
}
}
}
Making it Reactive
Now let's make the Adapter reactive 👇
By passing an onClickCallback into the MainViewHolder, subject.onNext(it) can be called inside.
class MainAdapter : ListAdapter<String, RecyclerView.ViewHolder>(...) {
+ val subject = PublishSubject.create<String>()
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val textToDisplay = getItem(position)
+ (holder as MainViewHolder).bind(textToDisplay) {
+ subject.onNext(it)
+ }
}
class MainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ fun bind(textToDisplay: String, onClickCallback: () -> Unit) {
itemView.apply {
rowTextView.text = textToDisplay
+ setOnClickListener { onClickCallback.invoke(textToDisplay) }
}
}
}
}
Now that MainAdapter is reactive, we can subscribe to it from the MainActivity:
val adapter = MainAdapter()
val list = listOf("row1", "row2", "row3")
fun onCreate() {
setContentView(R.layout.activity_reactive_views)
// setup RecyclerView
recyclerView.adapter = adapter
adapter.submitList(list)
+ adapter.subject
+ .subscribe { rowText ->
+ Log.d("debug", "${rowText} is clicked!")
+ }
}
That's all! The click event is now propagated from ViewHolder to Adapter, back to Activity upwards, by using Subject as a bridge.
Let's see how to make a call that requires startActivityForResult() and onActivityResult() to become reactive.
Using Subject for onActivityResult()
Let's say we have a MainActivity.kt trying to call another activity for picking an image, ImagePickingActivity.
On button click, we use startActivityForResult().
val imagePickedSubject:PublishSubject<Uri> = PublishSubject.create()
button.setOnClickListener {
val intent = Intent(activity, ImagePickingActivity::class.java)
startActivityForResult(intent, ImagePickingActivity.REQUEST_IMAGE_CAPTURE)
}
The result would then come back from onActivityResult():
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode) {
ImagePickingActivity.REQUEST_IMAGE_CAPTURE -> {
data?.getStringExtra(...)?.let { imagePath ->
+ imagePickedSubject.onNext(imagePath)
}
}
}
}
}
Since this is now reactive, it can be passed into the ViewModel, and being subscribed() by an observer to perform any actions.
class MainViewModel(val imagePickedObservable: Observable) {
fun onCreate() {
imagePickedObservable
.doOnNext { /* perform action */ }
.flatMap { /* perform action */ }
.subscribe { /* perform action */ }
}
}
That's it, it's pretty straight forward.
Things to becareful when Subject
1.
Since Subject is both receives and emits events. It should be handled with care. To make it clearer, let's consider this diagram:
In the diagram, Activity is supposed to be receiving events from the Subject, however, there's nothing preventing Activity from using this Subject from Adapter to re-emit events.
What we can do is to use the .hide() method from the subject, this will make a Subject to lose it's ability to re-emit events:
val observable = subject.hide()
However, the documentation says that by calling .hide(), some optimizations will be prevented, so it's probably good to not call this method, unless you're writing a library 💭 .
I recently learn that casting a Subject to Observable works too:
https://blog.kaush.co/2019/03/30/rx-tip-hide-your-subjects/

2.
Since Subject is a kind of bridge between the non-Rx and the Rx world, it's best not to use it when it's not necessary. Many times, we can get away with using other Observable creation methods: RxJava Observable creation methods.
Closing
Thanks for reading! Using RxJava is a very different paradigm, at the beginning it looks scary to me when I see a big chunk of Rx code, but in fact all it does it connecting things. It's mostly about some input, then some manipulation, then producing some outputs. I find the code to become easier to maintain in a long run.
I hope you find this post useful. 📚📚
See you next time 👋
Tan Jun Rong
Clap to support the author, help others find it, and make your opinion count.