Android: Reactive View Part II
Overview
This is the 2nd post of the 3 part blog posts:
- Android: Reactive View Part I
- Android: Reactive View Part II ← This Post Is Here!
- Android: Reactive View Part III.
A simple example is compared between reactive and non-reactive view layer in the previous post. In this post, let's look at a more complex example.
1. Reddit Example
Let's say we are building this:
Description
- when we click
Load More
, more posts will be loaded - when
Load More
is clicked again, the next page will be loaded - when
Refresh
is clicked, all current posts will be erased, and latest posts will be re-fetched - when
Change Subreddit
is clicked, all current posts will be erased, different posts will be fetched from another subreddit endpoint. (for people whose not familiar, subreddit is like a sub-topic in Reddit)
2. MVVM + Non-Reactive Views
This is the code for the Activity
and ViewModel
using the Non-Reactive View approach.
fun onCreate() {
rxViewLoadMoreButton.setOnClickListener {
viewModel.onLoadMoreClick()
}
rxViewRefreshButton.setOnClickListener {
viewModel.refreshClick()
}
rxViewChangeSubredditButton.setOnClickListener {
viewModel.randomSubredditClick()
}
}
fun onLoadMoreClick() {
loadMorePosts()
}
fun refreshClick() {
redditApi = RedditApi(redditApi.subreddit)
loadMorePosts()
}
fun randomSubredditClick() {
redditApi = RedditApi(RedditApi.getRandomSubreddit())
loadMorePosts()
}
fun loadMorePosts() {
redditApi.getMorePosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ newPosts ->
screenState.postValue(newPosts)
}, {
it.printStackTrace()
})
.addTo(disposables)
}
Similar to previous example, you will notice that in every button.onClickListener()
, you will always see viewModel.somethingClick()
. If you look inside the ViewModel
, you will see the all of 3 of the methods: refreshClick()
, randomSubredditClick()
and onLoadMoreClick()
, ultimately lead to calling loadMorePosts()
.
💡 This can all be merged together when the view layer is reactive. 💡
The full code can be found here:
Feel free to download the repo and take a closer look in Android Studio!
3. MVVM + Reactive Views
Let's check the implementation of Reactive Views.
val viewInput: RxReactiveViewsViewModel.ViewInput = object : SomeViewModel.ViewInput {
override val loadMoreClickObservable by lazy {
rxViewLoadMoreButton.clicks()
}
override val refreshClickObservable by lazy {
rxViewRefreshButton.clicks()
}
override val randomClickObservable by lazy {
rxViewChangeSubredditButton.clicks()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reactive_views)
rxViewRecyclerView.adapter = adapter
val viewModel = ViewModelProviders.of(this, SomeViewModel(viewInput)).get(SomeViewModel::class.java)
lifecycle.addObserver(viewModel)
viewModel.screenState.observe(this, Observer { list ->
adapter.submitList(list)
})
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
viewInput.apply {
Observable.merge(
loadMoreClickObservable,
refreshClickObservable.doOnNext { redditApi = RedditApi(redditApi.subreddit) },
randomClickObservable.doOnNext { redditApi = RedditApi(RedditApi.getRandomSubreddit()) }
)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.flatMap { redditApi.getMorePosts() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ newPosts ->
screenState.postValue(newPosts)
}
}, {
it.printStackTrace()
})
.addTo(disposables)
}
}
Now all the view clicks, are made reactive by calling button.clicks()
method from RxBinding library. They are passed into the ViewModel
as Observables
. Now it's up to the ViewModel
to decide how to connect them together.
Over here, Observable.merge()
is a suitable operator to do combine all the events together.
You can see from the diagram that all the streams are connected and merge()
into a single stream which ultimately calls redditApi.getMorePosts()
.
Take note of:
- how
refreshClickObservable
is usingdoOnNext()
to refresh theredditApi
- how
randomClickObservable
is usingdoOnNext()
to randomize theredditApi
- then both of them get merged into the same stream
Working Code
The working code is available at Github in as a Pull Request. By using a pull request format, you can see the exact diff of the files for making the views Reactive. Feel free to clone a copy and view in Android Studio.
Discussion
Now let's take a closer look at each of the diagrams.
Non-Reactive View | Reactive View |
---|---|
You can see that the non-reactive logic is doing a little bit of ping-ponging, where the activity has to call the onClick()
counterpart in the viewModel
methods. Besides, all the 3 methods inside the viewModel
has to call loadMore()
for 3 times.
In the reactive-view code, everything is interconnected together. 3 streams of clicks are all merged()
together into an ultimate stream.
It's arguable that which one is more readable by looking at the diff in the Pull Request. I think it really depends on one's familiarity of RxJava's syntax. However, if we only look at the image diagrams, it's clear that the reactive-view version is cleaner and more streamlined.
I noticed that the more I got familiarized with RxJava's syntax, the more I prefer to make the view layer reactive. Also, as the situation become more complex, reactive code will become easier to read than non-reactive code implementing the same thing.
4. A More Complete Example
The example used in this post is not yet the real production code, because the Load More
is triggered by a button instead of being triggered by reaching the bottom of the list. Internet connection is not checked. Refresh is done by a button instead of Pull-to-Refresh. Among other stuff...
If you're interested in a more complex example, feel free to check out this repo (github.com/worker8/Pixels) I made. It's a continuation of this post with all the drawbacks addressed and views being reactive.
Here's a screenshot of the app:
Here's the link to the viewModel
: https://github.com/worker8/Pixels/blob/master/app/src/main/java/beepbeep/pixelsforredditx/home/HomeViewModel.kt
5. Closing.. 🚪
It's a debatable topic which approach is better. There is certainly no one single way to write software. If you haven't try this Rx-View approach yet, I hope you would try out after reading this post.
If you're still not convinced about making the view reactive, perhaps I didn't do a good job in the post, or perhaps this approach doesn't suit your taste.
Anyhow, I hope you still learn something! 😄
Tan Jun Rong
Clap to support the author, help others find it, and make your opinion count.