flowOn and Buffering

flowOn Changes the Upstream Thread

flowOn(dispatcher) changes the dispatcher of everything above it. The collector keeps running in its own context. This keeps the UI thread free while the producer does I/O.

flow { read DB }      ┐
  .map { parse }      ├─ runs on Dispatchers.IO
  .flowOn(IO)         ┘
  .collect { show }   ── runs on the collector's dispatcher (Main)

Example

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun where() = Thread.currentThread().name.substringBefore("-worker")

fun main() = runBlocking {
    val emittedOn = mutableSetOf<String>()
    val collectedOn = mutableSetOf<String>()
    flow {
        for (i in 1..3) {
            emittedOn += where()
            emit(i)
        }
    }
        .map { it * 10 }
        .flowOn(Dispatchers.IO)
        .collect { collectedOn += where() }
    println("Emitted on:   $emittedOn")
    println("Collected on: $collectedOn")
}

Output

Emitted on:   [DefaultDispatcher]
Collected on: [main]

buffer, conflate, collectLatest

By default the producer waits while the collector works. Three operators change that.

OperatorProducerCollector gets
buffer()keeps producing into a bufferevery value
conflate()keeps producing, overwrites oldonly the latest when it is ready
collectLatest { }keeps producingcancels the slow block when a new value comes

Example

Producer needs 200 ms per item, collector 300 ms.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.system.measureTimeMillis

fun producer() = flow { for (i in 1..3) { delay(200); emit(i) } }

fun main() = runBlocking {
    val plain = measureTimeMillis { producer().collect { delay(300) } }
    val buffered = measureTimeMillis { producer().buffer().collect { delay(300) } }
    println("No buffer  ~1500 ms: ${plain >= 1500}")
    println("With buffer ~1100 ms: ${buffered < 1400}")

    val seen = mutableListOf<Int>()
    flow { for (i in 1..5) { delay(200); emit(i) } }
        .conflate()
        .collect { seen += it; delay(500) }
    println("conflate saw: $seen")
}

Output

No buffer  ~1500 ms: true
With buffer ~1100 ms: true
conflate saw: [1, 3, 5]