在 Composable 中收集转换后的 StateFlow

Collect transformed StateFlow in Composable

有函数collectAsState()适用于StateFlow 属性以便在Composable.

中观察它

可组合项需要 StateFlow,因为 StateFlow 保证初始值。 Flow 没有这种保证。

现在,如果我有 StateFlow 属性 但我想在收集 Flow 之前应用运算符(如 map),该怎么办在 Composable?

举个例子:

假设一个存储库公开了一个 StateFlow<MyClass>

val myClassStateFlow: StateFlow<MyClass>

data class MyClass(val a: String)

... 并且视图模型依赖于存储库并且只想将 属性 a 公开给它的 Composable...

val aFlow = myClassState.Flow.map { it.a } // <- this is of type Flow<String>

map 运算符将类型从 StateFlow<MyClass> 更改为 Flow<String>

  1. aFlow 不再具有初始值在语义上是否合理?毕竟它的第一次发射是从 myClassStateFlow.
  2. 的初始值导出的
  3. 需要在某个时候将 Flow 转换回 StateFlow。这是哪个更惯用的地方?
    1. 在视图模型中使用 stateIn()?代码会是什么样子?
    2. 在可组合项中使用 collectAsState(initial: MyClass) 并得出一个初始值(尽管 myClassStateFlow 有一个初始值)?

this issue on GitHub

目前没有内置的方法来转换 StateFlows,只有 Flows。但是你可以自己写。

我最终解决的方法是使用 post 中的示例。

首先创建一个DerivedStateFlow的概念。

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>) {
        flow.collect(collector)
    }
}

然后在 StateFlow 上进行扩展,就像在 Flow

上的当前 map 扩展一样
fun <T1, R> StateFlow<T1>.mapState(transform: (a: T1) -> R): StateFlow<R> {
    return DerivedStateFlow(
        getValue = { transform(this.value) },
        flow = this.map { a -> transform(a) }
    )
}

现在在您的 Repository 或 ViewModel 中,您可以按如下方式使用它。


class MyViewModel( ... ) {
    private val originalStateFlow:StateFlow<SomeT>  = ...
    
    val someStateFlowtoExposeToCompose = 
        originalStateFlow
        .mapState { item -> 
            yourTransform(item)
        }
}

现在您可以在 Compose 中按预期使用它而无需任何特殊工作,因为它 returns StateFlow.