Optimistic Updates with RxKotlin and ItemTouchHelper (Android)

Augustine Kwong
Aug 27, 2018 · 9 min read

One of the most frustrating and rewarding features I worked on during my co-op was reworking a settings page for users that featured drag-and-drop and optimistic updates. The problems that arose showed me the subtle complexities of such a feature. They also cautioned me to write simpler, readable (and ergo debuggable) code so design flaws are less often confused with technical bugs.

General use case: Manage groups of ordered items, where individual items can be moved between groups

User Story
As an Android Hootsuite user, I want to be able to control the content that is displayed to me. To that end, I should be able to group related social networks/streams together into tabs and rearrange the order of tabs as well as the streams within each tab so that the highest priority content streams are easily accessible and related content is grouped to my liking.

(content) streams :: Each social network (SN) provides different content sources. For example if you add a Twitter account, you might have a “My Posts” stream (featuring all your posts), or you can define your own by defining a search for a hashtag/query.

tab :: A grouping of content streams. Loading a tab will allow you to swipe across all the contained streams.


Problem Overview

(Left) Old tabs and streams management (Right) New tabs and streams management — what we’re building

Here’s a detailed breakdown of the features:

Before redesigning this functionality, our existing implementation featured some but not all of these features.

In addition to discoverability issues, a main source of gripe is the blocking ProgressDialog (deprecated) on each action. Therefore, it would be nice (but overwhelmingly frustrating for me) to add the following features:

Our implementation prioritizes:


Android UI components

From the problem overview, we can already gain a sense of the UI components we’ll need.

A list of heterogeneous items: A RecyclerView with multiple view types and view holders. We maintain an RecyclerView.Adapter<Any>; we forego adding an additional layer of abstraction e.g.RecyclerView<ManageItem> as there is no real added benefit. We will still leverage getItemViewType regardless. Our code base already abstracts the onBindViewHolder and onCreateViewHolder into a superclass with a SAM interface that handles interaction callbacks.

View interactions specific to view type: As mentioned above, our SAM interface will handle any effectful interactions.

Swipe-to-Delete and Drag-and-Drop: Use the ItemTouchHelper to animate and handle these interactions.

Architecture Pattern

First things first — what view architecture would make our lives easier? Lately, we’ve been moving more towards the Model-View-ViewModel (MVVM) architecture for Android features at Hootsuite. Here are several reasons why I believe this is the best pattern for this use case:

Simplified UML class diagram of implementation. Several classes omitted.

With optimistic updates, it would be nice to have a single-source of truth for UI state. MVC might do. But consider the fact that some changes to the model e.g. database update, do not reflect instantly on the view because we’re updating them optimistically and asynchronously. We want an intermediary for any change. In combination with Rx, ViewModel’s are up to the task! They will allow us to reactively coordinate changes from a multitude of view interactions.


Maintainability

I was delighted to find that our existing code base had an excellent abstraction for handling many different list item layouts in a single list (RecyclerView). The more interesting and challenging consideration was writing readable code; readability distinguishes easily maintainable and debuggable code from giant swamps of degrading code quality.

Here is an example where I made a tradeoff between the (negligible) space complexity of modifying a list in-place with the readability of more expressive code, taking advantage of Kotlin syntax.

The purpose of the functions are the same: given a list of items, we want to insert empty placeholders or remove them from the appropriate places. E.g. two Tabs in a row indicate one of them is empty and a placeholder should be inserted. We write it as an extension function since it is always used in the context of our list of data, thus we implicitly assign it the responsibility of re-arranging itself correctly.

Disgusting. It really is. But it totally works. What’s so bad about it?

Overall, the problem is this: the context of each line is hard to gleam from line to line. Where are we in the list? Where is the cursor? Where do we need the cursor to be in order to remove this item? In sum, in-place relies on side-effects. Side effects are sometimes hard to keep-track of and read.

Let’s try a slightly different, more functional fold approach; though not in-place, both implementations are still O(n).

Why do I consider this implementation superior?

For those unfamiliar with the fold method, here’s a quick review. Fold is a lot like a for-each loop, except that it returns a single value. Think of it as initializing an “accumulator” variable, and for each item in the collection, you will place into it a new value. At the very end, the value inside your accumulator is returned. Here are a few examples:

With our fold implementation, we are able to leverage the Kotlin’s syntax to improve readability. As Trevor Stokvis reminded me in conversation, it’s often useful to use method names as documentation. Here, the purpose of these functions are very clear. In fact, they even clarify the context at their call-site — something that we lost with the indexing of the iterator.

Finally, we can read the code in a simpler way: we are progressing through our list, slowly accumulating certain items. We don’t have to worry about backtracking and removing elements.


Smoothness

An important UX consideration is what to do when our optimism succeeds, i.e. the server accepts our change request.

One way to do optimistic updates is to allow the state change and further actions, and when the success response is received, we update the screen to the successful state.

Below is a demo on Android and iOS. In both, we are rearranging streams between and within a category. On the right, we see what happens when we re-draw the screen when our optimistic update query returns successfully. On the left is where we silently accept the success and don’t interrupt the user.

(Left) Android; (Right) iOS

In other words, the iOS implementation will replay successful actions, which interrupts the user and acts as a pseudo-blocking state.

What we want to do is only revert failure states. This means that we should be even more optimistic. This is where the big headache begins. We should be able to base our changes off uncommitted states, that we hope will soon be committed.

Action States

We convey the change using an PublishSubject that emits a sealed class

sealed class DataEvent(val data: List<Any>) {
class RefreshSuccess : DataEvent()
...
}
val subject: PublishSubject<DataEvent> = PublishSubject.create()

Then we define the different event states possible: RefreshSuccess, RefreshFailure, MovePending, MoveSuccess, MoveFailure, UndoPending, UndoSuccess, UndoFailure. This makes it easy to view each action flow as a series of finite-states, where the transition functions can be user input or api calls/responses. When testing the view model, we can mock/invoke these transitions between states and verify the result emitted by the view model subject.

The MoveSuccess is an interesting special case, since it’s meant to be “undoable”. We need to record the previous state as well as the desired state. Depending on your server endpoint, you have to consider how to reverse the state change. If all the logic is server-side, then you’re in luck! Sadly this wasn’t the case and the inverse move action had to be computed, sent, and handled.


Conclusion

This setup is quite flexible to extending functionality.

There are some considerations to make though:

Though I ran into many dead-ends and questions that led to more questions, slowly working through and iterating an implementation was incredibly rewarding. It wasn’t so much writing the code that tripped me up, the main consideration was modularizing each piece so that it could be changed or added to. An unexpected side effect was that it was much easier to diagnose and improve my solution. Because all the code for a particular functionality had a logical place, it was easy to form hypothesis and work it out.

That being said, all the credit goes to the amazing UX team and mobile team I had the pleasure of working with here at Hootsuite. The support, patience and cordiality shown to me kept me motivated and excited about implementing from a user-minded perspective.

About the author

Augustine Kwong is a Combined Major in Computer Science and Statistics at the University of British Columbia. He worked on the Core Mobile team, working predominantly on the Android app but branched out to work on backend in Scala.

Hootsuite Engineering

Hootsuite's Engineering Blog

Augustine Kwong

Written by

Coop Developer. UBC Combined Major CPSC & STAT. http://github.com/augudyne.

Hootsuite Engineering

Hootsuite's Engineering Blog

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade