17

With LiveData inside a Viewmodel we use switchMap or Transformations.map like this

val recipesList = cuisineType.switchMap { repository.getDisplayRecipes(it.cuisineType).asLiveData() }

What would be the best way to do this with StateFlow? I know we can just use map like below, however this this would return me Flow<Flow<List< Recipe>>> which doesn't seem correct

val recipeListFlow = cuisineTypeStateFlow.map {
    repository.getDisplayRecipes(it.cuisineType)
}
beigirad
  • 4,986
  • 2
  • 29
  • 52
alfietap
  • 1,585
  • 1
  • 15
  • 38
  • 4
    You have to use `stateIn()` to convert the secondary flow into a StateFlow. They don't provide transformation functions that return a hot flow (yet?). – Tenfour04 Feb 07 '21 at 21:36

2 Answers2

4

There is still no official way to do this (yet) but there is a reasonable recommendation from https://github.com/Kotlin/kotlinx.coroutines/issues/2631#issuecomment-870565860 :

/**
 * Does not produce the same value in a raw, so respect "distinct until changed emissions"
 * */
class DerivedStateFlow<T>(
    private val getValue: () -> T,
    private val flow: Flow<T>
) : StateFlow<T> {

    override val replayCache: List<T>
        get () = listOf(value)

    override val value: T
        get () = getValue()

    @InternalCoroutinesApi
    override suspend fun collect(collector: FlowCollector<T>): Nothing {
        coroutineScope { flow.distinctUntilChanged().stateIn(this).collect(collector) }
    }
}

fun <T1, R> StateFlow<T1>.mapState(transform: (a: T1) -> R): StateFlow<R> {
    return DerivedStateFlow(
        getValue = { transform(this.value) },
        flow = this.map { a -> transform(a) }
    )
}

fun <T1, T2, R> combineStates(flow: StateFlow<T1>, flow2: StateFlow<T2>, transform: (a: T1, b: T2) -> R): StateFlow<R> {
    return DerivedStateFlow(
        getValue = { transform(flow.value, flow2.value) },
        flow = combine(flow, flow2) { a, b -> transform(a, b) }
    )
}

// and so on
Brian Yencho
  • 2,748
  • 1
  • 18
  • 19
1

Should be

val recipeListFlow = cuisineTypeStateFlow.flatMapLatest {
    repository.getDisplayRecipes(it.cuisineType)
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • 2
    If I understand correctly, this solves the problem of `map` emiting obsolete values, but you’re still stuck with a cold flow after the transformation? – Tenfour04 Feb 08 '21 at 01:03
  • Just to give some more context, in my fragment class I then want to collect the flow inside recipeListFlow and use it to submit a list to a list adapter – alfietap Feb 08 '21 at 06:10