【问题标题】:How to .collectAsState() from a StateFlow using repeatOnLifecycle() in a @Composable?如何在 @Composable 中使用 repeatOnLifecycle() 从 StateFlow 中 .collectAsState()?
【发布时间】:2022-01-09 21:32:20
【问题描述】:

根据这些文章,我正在尝试按照官方指南使用 Compose 从 LiveData 迁移到 Flow/StateFlow:

A safer way to collect flows from Android UIs

Migrating from LiveData to Kotlin’s Flow

我正在尝试遵循第一篇文章中的建议,在接近结尾的 Jetpack Compose 中的安全流程集合部分。

在 Compose 中,副作用必须以受控的方式执行 环境。为此,使用 LaunchedEffect 创建一个协程 遵循可组合的生命周期。在其块中,您可以调用 如果需要重新启动 Lifecycle.repeatOnLifecycle,请暂停 Lifecycle.repeatOnLifecycle 主机生命周期处于特定状态时的代码块。

我已经设法以这种方式使用.flowWithLifecycle() 来确保当应用程序进入后台时流不会发出:

@Composable
fun MyScreen() {

    val lifecycleOwner = LocalLifecycleOwner.current

    val someState = remember(viewModel.someFlow, lifecycleOwner) {
        viewModel.someFlow
            .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
            .stateIn(
                scope = viewModel.viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = null
            )
    }.collectAsState()

}

我觉得这很“样板”——一定有更好的东西。我想在 ViewModel 中使用 StateFlow,而不是在 @Composable 中转换为 StateFLow 的 Flow,并使用 .repeatOnLifeCycle(),因此我可以使用多个 .collectAsState() 并减少样板。

当我尝试在协程 (LaunchedEffect) 中使用 .collectAsState() 时,我显然会收到关于必须从 @Composable 函数的上下文中调用 .collectAsState() 的错误。

如何实现与 .collectAsState() 类似的功能,但在 .repeatOnLifecycle() 内部。我是否必须在 StateFlow 上使用 .collect() 然后用 State 包装值?没有比这更少的样板吗?

【问题讨论】:

    标签: android kotlin android-jetpack-compose kotlin-coroutines kotlin-flow


    【解决方案1】:

    在阅读了几篇文章后,包括

    Things to know about Flow’s shareIn and stateIn operators

    repeatOnLifecycle API design story

    我越来越相信以这种方式使用 .flowWithLifecycle() 而不是 repeatOnLifecycle() +produceState()。我只是做了这个扩展函数来减轻从@Composable 收集流作为状态时的样板:

    @Composable
    fun <T> Flow<T>.flowWithLifecycleStateInAndCollectAsState(
        scope: CoroutineScope,
        initial: T? = null,
        context: CoroutineContext = EmptyCoroutineContext,
    ): State<T?> {
        val lifecycleOwner = LocalLifecycleOwner.current
        return remember(this, lifecycleOwner) {
            this
                .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
                .stateIn(
                    scope = scope,
                    started = SharingStarted.WhileSubscribed(5000),
                    initialValue = initial
                )
        }.collectAsState(context)
    }
    

    这将在 @Composable 中像这样使用:

    @Composable
    fun SomeScreen() {
    
    //...
    
        val someState = viewModel.someFlow
            .flowWithLifecycleStateInAndCollectAsState(
                scope = viewModel.viewModelScope  //or the composable's scope
            )
    
    //...
    
    }
    

    我会将其标记为已接受的解决方案,以防它对其他人有所帮助,除非有人在接下来的几天内让我知道更好的方法。

    【讨论】:

    • context: CoroutineContext = EmptyCoroutineContext // 起什么作用?
    • @ShinDongHwi collectAsState() 可以采用 CoroutineContext 参数。如果不需要传递 CoroutineContext,则会传递一个默认值 EmptyCoroutineContext。
    【解决方案2】:

    根据 OP 的回答,如果您不关心 WhileSubscribed(5000) 的行为,则内部不通过 StateFlow 可能会更轻量级。

    @Composable
    fun <T> Flow<T>.toStateWhenStarted(initialValue: T): State<T> {
        val lifecycleOwner = LocalLifecycleOwner.current
        return produceState(initialValue = initialValue, this, lifecycleOwner) {
            lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                collect { value = it }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2022-01-06
      • 1970-01-01
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多