Coroutines — Non I/O blocking operations

Peter Marinov
The Startup
Published in
3 min readOct 25, 2019
Poland, Kotlin – old house

Here at Trading212, we’re big fans of Kotlin! Even more of Kotlin Coroutines. Coroutines are the best tool for handling concurrency and parallel execution, but this is just the tip of the iceberg.

When you think about it, everything in the mobile world is in some form asynchronous. Opening an activity, presenting a dialog (with animation), asking for permission to use the SD storage and so on. Most of these, non-IO, but blocking operations (at least for the programmer) need some post factum handling.

Here’s an example… I want to scroll a list to a specific item and then display an overlay over it. This broken down into actions is:

  • Set a stop scroll listener on the recycler view (1)
  • Start the scroll (2)
  • Detect stop (3)
  • Draw the overlay. (4)

Simple and concise, nothing can go wrong here… that is if the compiler knew English. What it looks like in practice:

// Set a scroll listener (1)
recyclerView.addOnScrollListener(
object : RecyclerView.OnScrollListener()
{
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int
){
super.onScrollStateChanged(
recyclerView,
newState
)
// Detect stop (3)
if (newState == RecyclerView.SCROLL_STATE_IDLE)
{
recyclerView.removeOnScrollListener(this)

// Draw overlay (4)
drawOverlay()
}
}
}
)
// Start the scroll (2)
linearLayoutManager.scrollToPositionWithOffset(position, offset);

I’ve numbered the four instructions so it’s easier to explain. It becomes apparent that these actions won’t happen linearly, in an order that we could easily follow.

What if we could do this instead

// (1), (2), (3) into one
recyclerView.awaitScrollToPositionWithOffset(position, offset)
// (4)
drawOverlay()

Much more readable and in the future, debuggable. “But wait” you’d say, “there is no such method in the RecyclerView API”. This is where Kotlin extension functions swoop in to save the day.

This extension is generic enough, to be used everywhere and is isolated enough, so that we can identify problems with scroll-and-wait behavior.

Another example, for an interesting Coroutines usage, is waiting for an external event in a linear fashion. Imagine that your app lists notifications, which are marked as acknowledged when a user taps on them.

Trading212 — Android App

(Let’s assume for this example, that we don’t care about the HTTP request result (fire-and-forget). And the verification comes through a web-socket event.)

The process, broken down into actions:

  • Register for acknowledgment verification event (1)
  • Fire acknowledge request (2)
  • Receive event (3)
  • Remove the flashing overlay (4)
val receiver =
object : BroadcastReceiver()
{
override fun onReceive(
context: Context?, intent: Intent?)
{
// Receive event (3)

// Remove flashing overlay (4)
removeFlashingOverlay()
}
}

// Register for acknowledgment verification event (1)
LocalBroadcastManager.getInstance(context)
.registerReceiver(receiver, IntentFilter("Event#Ack"))

// Fire acknowledge request (2)
acknowledgeNotification()

Hard to follow and messy. My proposition:

// Send the request
acknowledgeNotification()

// Wait for the confirmation event or 10 sec timeout
"Event#Ack".waitForBroadcastWithTimeout(context, 10_000)

removeFlashingOverlay()

waitForBroadcastWithTimeout is defined for String with a version without timeout — waitForBroadcast.

Additionally, a trigger lambda can be defined if the timing of the calls is important. In our case, if the back-end sends the socket event before we’ve hooked the broadcast receiver, we will miss the event.

"Event#Ack".waitForBroadcastWithTimeout(context, 10_000) {    acknowledgeNotification()
}
removeFlashingOverlay()

This way we are guaranteed to never miss our event!

These are relatively simple examples, that hint to the awesome power at our disposal. Some other interesting usages of coroutines I’ve seen include:

But as we know, with great power comes great responsibility. The example I gave with waitForBroadcast is not to be abused. When hiding away complexity we don’t forgo the performance toll. Even more, we pay a bit extra. Never the less, it’s worth thinking about improving the readability of asynchronous code via Coroutines.

Thanks for reading! As always, Make Kotlin, not Java!

Disclaimer: The snippets shared are based on, but not exactly, the code we use in production (primarily, because of extra stuff we have defined). So they should work correctly, but are not battle-tested.

--

--