8

I need to handle current and previous value in flow collect, so I need some operator that acts like that:

----A----------B-------C-----|--->

---(null+A)---(A+B)---(B+C)--|--->

One idea is something like:

fun <T: Any> Flow<T>.withPrevious(): Flow<Pair<T?, T>> = flow {
    var prev: T? = null
    this@withPrevious.collect {
        emit(prev to it)
        prev = it
    }
}

But this way there is no control over a context in which first flow will be executed. Is there more flexible solution?

Sergio
  • 27,326
  • 8
  • 128
  • 149
Vlad Kudoyar
  • 371
  • 3
  • 8
  • what do you mean by "there is no control over a context in which first flow will be executed"? you can use `flowOn` – IR42 Jun 15 '22 at 08:29
  • @IR42 for example: `someFunc().withPrev().map { / something / }.flowOn(customContext)`. So in this example flowOn will be applied only on `map` but not on `someFunc` – Vlad Kudoyar Jun 15 '22 at 14:35
  • 2
    it's not true, did you test it? flowOn will also be applied to withPrevious and all "preceding operators that do not have its own context" – IR42 Jun 15 '22 at 14:44
  • You are right, flowOn acts on first flow too. So seems this code works as expected. Thanks :) – Vlad Kudoyar Jun 16 '22 at 17:17

2 Answers2

6

There's an operator which makes this very easy: runningFold

The docs have an example on how to use it to collect each emission of a flow; this can be easily adapted to fit our needs

data class History<T>(val previous: T?, val current: T)

// emits null, History(null,1), History(1,2)...
fun <T> Flow<T>.runningHistory(): Flow<History<T>?> =
    runningFold(
        initial = null as (History<T>?),
        operation = { accumulator, new -> History(accumulator?.current, new) }
    )

// doesn't emit until first value History(null,1), History(1,2)...
fun <T> Flow<T>.runningHistoryAlternative(): Flow<History<T>> =
    runningHistory().filterNotNull()

You might need to tweak nullabilities to fit your usecase

m.reiter
  • 1,796
  • 2
  • 11
  • 31
4

Flows are sequential, so you can use a variable to store the previous value:

coroutineScope.launch {
    var prevValue = null
    flow.collect { newValue ->
        // use prevValue and newValue here
        ...
        // update prevValue
        prevValue = newValue
    }
}
Sergio
  • 27,326
  • 8
  • 128
  • 149
  • Hey, wouldn't prevValue be always null in this case when the flow starts emitting? How could you make prevValue equals first value emitted from the flow? – bromden Apr 21 '23 at 08:25
  • It will be `null` during the first emission. Starting from the second emission and so on, if `newValue` is not `null`, `prevValue` will we updated. – Sergio Apr 21 '23 at 09:06
  • Yep, any ideas on how could you make prevValue equals first value emitted from the flow? – bromden Apr 21 '23 at 09:33
  • Probably `flow.first()`? – Sergio Apr 21 '23 at 09:57