Many modern mobile applications build some kind of a caching layer, which is used to locally store network data. Then, after initial caching is done, data will be presented to the user almost immediately instead of reaching to the network and performing fetching-and-parsing dance each time data is needed.

Usually, this mechanism is hidden behind some abstraction so that the presentation layer doesn’t know where the data is coming from: local storage, network, sibling app’s content provider or from some weird mix of all of those. This also means that the presentation layer won’t know and can’t readily predict in advance how long data loading would take: it can be fast (up to 300–400 milliseconds) or slow (several seconds). Actually, even in absence of a caching mechanism, prediction based on data source type is not to be trusted: occasionally reading from sdcard can be slow and reading from a network can be very fast.

This presents few strategies on how to display a loading state in UI in absence of hint about loading times. Let’s take the simplest UI with two states — Loading (progress bar) and Content (logo image) as an example.

Application author may decide to simply switch to a Loading state once data loading has started and then switch to a Content state once data becomes available. This will look nice only when data loading task is time-consuming. Otherwise if data loads quickly user will see something like this:

Data loads quickly, leaving little time to show the progress bar

Which is not very pretty — it looks jumpy and junky even with the standard progress bar, but if you have a loading state with some custom animation it may feel even worse.

There’s a known trick to cope with this which is to delay showing Loading state for a short period of time (say 400ms) and once it passes — check if data was received and if it was — show a Content state immediately and skip showing Loading state entirely. Here’s what the “slow data path” looks like, i.e. when receiving data takes a long time and Loading state must be shown:

400ms of “checking”-delay → 2000ms of Loading state → Content state

This also means that for this short period of time (until it is clear whether Loading state should be skipped) user will always see some “blank” state — no Loading and no Content, but usually it’s not a problem and is barely noticeable (did you notice 400ms starting delay after a button is clicked in the above animation?).

Here is a “quick data path” where after 400ms Loading state is skipped:

TAP → 400ms of “checking”-delay → Content state

There is another issue to tackle here: what if data wasn’t quick to load and so a Loading state kicked in, but then after say 50 milliseconds a Content arrived! If the application was to show it immediately, this would still cause flicker, because Loading state would be shown only for 50 milliseconds (400ms delay → 50ms Loading → Content). Because of this when using this trick you’d also have to forcefully delay showing content state (in “slow path” only) for a period of time which you consider enough for Loading state to be nicely perceived by the user.

Here’s an example demonstrating situation when Content state is loaded close after Loading state (grey logo), but user-visible change would be delayed by 2000ms (blue logo).

Now, what are the solutions which help to implement this trick? There’s not so widely known class in Android SDK called ContentLoadingProgressBar. There’s also a more recent ProgressTimeLatch class introduced by Chris Banes which is more flexible and can be used to the same effect on any view. The project I work on has adopted an RxJava2-based reactive approach for managing an application state, this now includes both business logic layer and a presentation layer (which is based on the MVI pattern). This means that UI is represented by a stream of states. And naturally I wanted some solution which would allow me to play the “delay”-trick described above on such a stream of states.

At first I thought I could express all this peculiar behavior using some combination of RxJava operators and I tried some of them (buffer, zip, delay, debounce) — it took me several days and several crazy-combination ideas, but I failed miserably. Each solution achieved one part of the trick but failed the other. So I decided to do another seemingly crazy thing — try to write my own RxJava operator which would allow me to call onNext when I want to. Considering my not so perfect concurrency skills and even less perfect (equal to zero) skills of writing RxJava operators I was both afraid and excited :) But I managed to do it! Let me demonstrate briefly.

Let’s create a basic class representing a Loading-Content state (this could also be expressed as a sealed class with two entries, but oh well):

data class LCState<T>(

val isLoading: Boolean,

val content: T

)

The UI used as an example in this post could be described as Observable<LCState<Bitmap>> . I created an operator which I called “lceFilterDelay”¹. You use it on a state stream and the only thing you need to do is provide a predicate to help distinguish loading state from content state and specify delay times:

stateStream

.lceFilterDelay(

// predicate

loadingItemPredicate = { it.isLoading }, // time in milliseconds to wait for content state,

// skip L if C comes during this period

acceptableContentItemDelay = 400, // time in milliseconds to delay C on "slow path"

minDelayFromLoadingItem = 2000)

.subscribe(...)

Operator will enforce the behavior described in this post by skipping a Loading state and delaying a Content state as needed.

The operator is available as a gist. If you feel adventurous enough to try it, I must warn you:

I didn’t put it through a whole lot of testing (though I tested how it works on a few screens and plan to integrate it on even more of them to uncover bugs); I’m not very sure I got it right. It was my first Rx operator; I’m not very experienced in writing a solid concurrent code.

With all that said I would be very grateful for code reviews from people who know this stuff! It would be even better if someone would find a way to achieve same behavior without writing custom operator and by using only standard ones.

Happy state-streaming!