11

Misnomers, Mistakes and Misunderstandings to watch for when learning Kotlin Coro...

 3 years ago
source link: https://medium.com/google-developer-experts/misnomers-mistakes-and-misunderstandings-to-watch-for-when-learning-kotlin-coroutines-and-flow-2744186be3e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Misnomers, Mistakes and Misunderstandings to watch for when learning Kotlin Coroutines and Flow

While learning to use Coroutines and Flow in several different projects (including my own), I noticed some common anti-patterns that tend to arise while coming to terms with structured concurrency and reactive streams. In this article, I will outline how to recognise and refactor four of the most common cases.

1*hiaL58lQdsOjXdL_CERd0g.jpeg?q=20
misnomers-mistakes-and-misunderstandings-to-watch-for-when-learning-kotlin-coroutines-and-flow-2744186be3e
Keep your eyes open! A Tarsier I met near Bitung, Sulawesi

1. Using Flow instead of a suspend function

When designing your methods and functions, start with a suspend function and if you get stuck, change it to a Flow.

Consider this typical function signature for calling a RESTful API.

Such a function is especially common in projects that are migrating from RxJava where we have the concept of a Single

A Single means that you are expecting to receive a single event from your stream, as opposed to many. A single event...doesn't really sound like a “stream”, does it? More like a drop than a stream. That is why Single doesn't exist in Flow as an explicit type, rather as an operator: Flow.single().

But don’t use an operator! It is only useful if you need to merge or combine streams for some reason… there is no need to complicate things — if you are expecting a single event — just return it!

A suspend function should be the first tool you pull out of the box, mostly because it is just the right choice for making readable code, but also because it can easily be refactored to a Flow if you want to experiment with a Flow later as well:

The key question to ask yourself is — am I expecting one thing (suspend function) or many things (Flow)?

2. Suspend functions with callback parameters

Suspend functions are a direct replacement for callbacks — it is one of the basic premises behind structured concurrency. Callbacks make code hard to follow (ie. they make your code unstructured). The classic example of this is the pyramid of doom or callback hell. Coroutines (represented primarily as suspend functions in Kotlin) are designed to clean that kind of code up — callbacks are no longer required:

Here is a more complex example with multiple callbacks:

How do you convert this to a suspend function? If you look carefully, you can see that only one callback will be called, depending on whether the call succeeds or not. In the old Java days, we would have created a Listener interface and sent an inline implementation. Curiously, the Kotlin/Coroutine solution has a similar feel to it, even though it is technically very different:

But what if we are downloading a large file and want progress too?

Now we are expecting multiple calls so we need to refactor to a Flow:

Notice that if we are returning a Flow, we no longer need the suspend attribute

Check out my article on Converting Callbacks to Flow if you’re interested in the implementation of the above functions.

3. Using GlobalScope

This Kotlin Documentation for Coroutines explicitly warns against using the GlobalScope.

Activity, Fragment, ViewModel, View... they all have corresponding lifecycle extensions which you should use to launch Coroutines. Use these extensions to launch liberally and launch often, as Coroutines, unlike Threads, are cheap and lightweight. You can launch thousands of them and there is negligible overhead - it is only important that you tie the Coroutines to a lifecycle to avoid memory leaks.

4. Unnecessary dispatching

If you do need to bounce into another thread, do it at the latest possible moment. It’s very easy to bounce in and out of threads with coroutines, but never write defensive code unless you absolutely have to. Most modern IO libraries that support coroutines will take care of this marshalling internally so you don’t even need to think unless you are hitting the metal yourself.

The benefits, apart from cleanliness, lie in testing. As soon as you start messing with Dispatchers, you have to start injecting a DispatcherProvider so that you can unit test properly. It’s clever, but still extra overhead that is often unnecessary.

Teams that are used to juggling Threads on RxJava will struggle with this concept and will often write very defensive code while migrating to Flow from RxJava:

RxJava, like vanilla Callbacks, don’t guarantee which thread you are called back on — so you have to be defensive and explicitly marshal your callback closure to the MainThread. This is another goal behind structured concurrency and is addressed in Kotlin with Context Preservation.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK