Kotlin Flow: Best Practices

Florent Blot
4 min readAug 3, 2022

--

Photo by Jens Aber on Unsplash

Since few months, @Geev, we used Kotlin Flow inside our Android application. It is a really beautiful library to use, it helps us to write efficient streams of data. The following few points are what we believe to be our base of best practices.

No Kotlin Flow 🙅‍♀️

This first point may be the simplest: do not use Kotlin Flow if you are not consuming streams.

Indeed, for one-shot requests, it’s preferably to apply suspended functions. No need to create a stream when we only send data to a server or receive a data object from an API just one-time. No need to overkill your app 😉

Use catch operator 🕵️‍♀️

By using the catch operator, we increase control and readability.

When we deal with a flow, we could have an unexpected error. In order to prevent our app to crash, we have to catch these kind of exceptions. A basic way would be to surround the flow with a try/catch close.

try { getItems().map { … }.collect { … } } catch (e: Exception) { println(“Caught: $e”) }

However, it is better to catch the exception directly inside our flow with catch. This operator catches upstream flows errors and can also emit new values to downstream flows. It makes our stream more concise too.

getItems().catch { println(“error in flow: $it”) }.map { … }.catch { println(“error in mapping: $it”) }.collect { … }

Play with launchIn 👩‍✈️

Prefer to decrease the verbosity of the flow collecting with launchIn.

We often launch our flow collection in a CoroutineScope bounded to a lifecycle, as the ViewModel’s scope.

viewModelScope.launch { getItems().map { … }.catch { … }.collect { println(it) } }

We might want to remove the outer brackets and to reduce this boilerplate.

getItems().map { … }.onEach { println(it) }.catch { … }.launchIn(viewModelScope)

launchIn combines launch and collect. It is a final operator, and this is why we need to apply onEach operator to get the data before collecting.

On a side note, in our previous snippet, if an error occurred in collect, it was not caught. With the new implementation, the catch operator is below onEach, therefore it secures the collecting.

Inject Dispatcher in flowOn 🥷

We must inject Dispatcher to change the flows context.

Changing the context of flows is done using flowOn. We could switch the execution of the upstream flows to IO Dispatcher as follows:

getItems().flowOn(context = Dispatcher.IO).map { … }.flowOn(context = Dispatcher.Default).catch { … }.launchIn(viewModelScope)

Hardcoded these Dispatchers can lead to unexpected behaviors when writing tests and to be very painful to control the flow’s context. Instead of the above, we should inject them and replace them in unit and instrumentation tests with a test dispatcher.

class ItemsViewModel(private val ioDispatcher = Dispatchers.IO; private val defaultDispatcher = Dispatchers.Default) : ViewModel() { fun getItems() { getItems().flowOn(context = ioDispatcher).map { … }.flowOn(context = defaultDispatcher).catch { … }.launchIn(viewModelScope) } }

Expose immutable StateFlow 👮‍♀️

When updating a StateFlow value, we have to maintain the immutable state of this flow.

Do not directly use mutable StateFlow from other classes. This does not guarantees the flow’s state because it could be updated from another class. It might lead to unpleasant debug when something goes wrong.

class ItemsViewModel(…) : ViewModel() { val itemsState = MutableStateFlow(ItemsState.Loading) }

We must ensure the centralization of the updating state in one class only making the debugging easier.

class ItemsViewModel(…) : ViewModel() { private val _itemsState = MutableStateFlow(ItemsState.Loading); val itemsState = _itemsState.asStateFlow(); }

Best Practices 👨‍🎤

We follow these few tricks when using Kotlin Flow in our app. We thought that it improved our efficiency and the stability of our platform. These enhancements helped us for writing greater code and better tests.

--

--

Florent Blot
Florent Blot

Written by Florent Blot

Maker, Mobile developer, Kotlin & Dart enthusiast, IoT & DIY addict. @Geev: https://www.geev.com

Responses (3)