article banner (priority)

Suspending functions vs. Flow: When to use which?

Modern coroutines-friendly APIs provide two kinds of functions:

  • Suspending functions that return regular result.
  • Regular functions returning Flow with result type.

For instance, you can use both those types of functions when you define a Room Dao (persistence library for SQLite).

A general recommendation for libraries is simple:

  • Use suspending functions when your function is meant to just result with one value, like when you request something from a database of from external service.
  • Use regular functions with Flow result when unknown number of results is possible, like when you represent a WebSocket connection or an observer on your database.

Room (like most libraries) follows those recommendations perfectly. observeUsers in the above code returns Flow, because it is an observer, that emits new list of users every time this table changes.

It is not required, but recommended, to follow the same guidelines in applications.

It is generally not a good practice to use Flow for cases where you expect exactly one result. Why?

  • It is misleading, Flow suggests multiple results.
  • It is just easier to work on suspending functions. Suspending functions are implemented with simple patterns, can be easily debugged, are readable even to developers unfamiliar with Kotlin.

Why so often I can see Flow or RxJava used everywhere?

Nevertheless, I could work or review many projects that used Flow everywhere, from the bottom to the top, even though most functions only emitted one value. Why? I believe the key reason is legacy. Those projects, or those developers, used to implement their projects with RxJava (I will use this term to refer to RxJava 2, RxJava 3 and Reactor). RxJava is a very invasive library. It has its own cancellation mechanism, inconsistent with Java interruption, it has its own schedulers, inconsistent with Java executors.

Think of a backend application that needs to handle some HTTP requests and some WebSockets. For handling requests, it would be best to use blocking functions and project Loom. Though we also need to handle WebSockets, and for developers might decide to use RxJava. However, it is very likely those two will need to operate on the same domain or repository functions, and RxJava is based on completely different cancellation and thread management mechanisms than blocking functions. Managing that would be a nightmare, so as I’ve noticed, developers typically choose to use RxJava types everywhere. This complicates code astronomically! What could have been implemented using simple constructs, now needs a convoluted use of different functions from RxJava.

It is time to day, that suspending functions and Flow do not have those problems. They share the same mechanism, so they have consistent cancellation, thread management, structured concurrency etc. Flow is just a simple wrapper over suspending functions. You can safely interoperate between suspending functions and Flow. This is a huge Kotlin advantage. This way both kinds of functions can be represented accordingly.

What about Android?

However, Android have one last thing to say. In Android view models, observable state is represented with StateFlow. API that exposes Flow is just easier to consume, because it can be turned into StateFlow using stateIn. I accept this argument, and I accept using Flow everywhere in applications with very simple domain. Most small applications are tiny clients, if you do nothing in your upper layers, the difference if neglectible. However, once domain starts growing, using flows starts being a burden. You should also know that all the functions we have for Flow, can be simply reproduced for suspending functions. I often implement my own retry or mapAsync. You can also define helpers to easily turn suspending functions into StateFlow, like this one I invented and I am now experimenting with:

I hope this clarifies my stand on this topic. You should decide what to do, remembering to respect team decisions above personal preferences, or even above general best practices.