Android: Reactive View Part II

Tan Jun Rong avatar

Tan Jun Rong

Overview

This is the 2nd post of the 3 part blog posts:

  1. Android: Reactive View Part I
  2. Android: Reactive View Part II ← This Post Is Here!
  3. Android: Reactive View Part III.
Photo by Jf Brou on Unsplash
Photo by Jf Brou on Unsplash

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:

Simple One Page Reddit Client
Simple One Page Reddit Client

Description

  1. when we click Load More, more posts will be loaded
  2. when Load More is clicked again, the next page will be loaded
  3. when Refresh is clicked, all current posts will be erased, and latest posts will be re-fetched
  4. 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. 💡

Non-Reactive Flow Diagram
Non-Reactive Flow Diagram

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.

Reactive View Flow Diagram
Reactive View Flow Diagram

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 using doOnNext() to refresh the redditApi
  • how randomClickObservable is using doOnNext() to randomize the redditApi
  • 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 avatar
Written By

Tan Jun Rong

Android Programmer who likes writing blogs, reading, coffee, snowboarding.
Published in Android
Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.