【问题标题】:Unsubscribe from Kotlin Flow without cancelling current scope在不取消当前范围的情况下退订 Kotlin Flow
【发布时间】:2022-10-15 17:17:33
【问题描述】:

我在 Android 的 repeatOnLifecycle 块内订阅 Kotlin SharedFlow 布尔值。我想订阅,直到收到第一个 true 布尔值并采取行动。

一旦收到第一个 true 布尔值,我需要取消订阅并在生命周期范围内运行处理函数,以便在生命周期转换为无效状态时取消它。

当我在当前范围上调用取消并将处理代码嵌入NonCancellable 上下文时,它不会在生命周期事件中被取消。

我想我想要takeWhile 之类的东西,包括与谓词不匹配的第一个元素。

下面是一些示例流程,我想在其中收集所有元素,直到 $ 符号:

true $ ...
false, true $ ...
false, false, true $ ...

示例代码:

viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        flowOfInterest.collectLatest {
            if (it) {
                stopCollection()
                doOnTrue()
            } else {
                doOnFalse()
            }
        }
    }
}

实现此行为的正确/最简单方法是什么?

谢谢!

【问题讨论】:

    标签: android kotlin kotlin-coroutines android-lifecycle kotlin-flow


    【解决方案1】:

    您可以使用first 函数继续收集,直到给定谓词返回true

    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            flowOfInterest.first { 
                if(it) doOnTrue() else doOnFalse()
                it 
            }
        }
    }
    

    【讨论】:

    • 感谢!我不知道 first 也可以带有谓词。我认为这非常接近我所需要的,但是它不会返回与谓词不匹配的元素,直到第一次匹配。我需要所有不匹配的内容,包括第一个匹配元素。
    • 您可以创建一个可变列表并继续在 lambda 中添加列表中的项目。我不明白你在这里面临什么问题。你能详细说明一下吗?
    • 我认为你是对的,如果我调用谓词中的函数,first 会起作用,就像你的例子一样。我不t remember why I assumed that this wouldnt 工作。明天我会再看一下实施情况,然后回复你。
    【解决方案2】:

    在这里回答我自己的问题。我需要的是一个类似于takeWhile 的运算符,其中包含第一个不匹配的元素。可以使用transformWhile 运算符创建这样的运算符,如下所示:

    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            flowOfInterest.transformWhile {
                emit(it)
                !it
            }.collectLatest {
                if (it) {
                    doOnTrue()
                } else {
                    doOnFalse()
                }
            }
        }
    }
    

    这不像我希望的那么好和紧凑,但它确实有效。

    编辑:或者,您可以使用 Arpit Shukla 的答案并在“第一个”函数的谓词中执行操作。

    【讨论】:

    • 嘿,你能解释一下为什么first 不适合你吗?
    • 当然,让我们在下面讨论您的答案。
    【解决方案3】:

    你可以在repeatOnLifecycle 函数范围内启动一个新的协程,因为launch 函数返回一个Job,所以你可以在以后使用它来取消你的工作。

    此外,如果你想在repeatOnLifecycle 范围内监听多个流,你需要启动一个子协程让它们并行运行。所以它们不会互相阻塞,取消一个流不会影响其他流。

    示例代码:

    lateinit var job: Job
    
    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            job = launch {
                flowOfInterest.collectLatest {
                    if (it) {
                        stopCollection()
                        doOnTrue()
                    } else {
                        doOnFalse()
                    }
                }
            }
        }
    }
    
    func stopCollection() {
        job.cancel()
    }
    

    【讨论】:

    • 也许我错了,但在这种情况下,从doOnTrue(或 doOnTrue 本身)调用的挂起函数也被取消了吗?
    【解决方案4】:

    您还可以使用takeWhile 定义控制从流中收集的条件。

     flowOfInterest.takeWhile { !it }.collect {
         //execute smth
     }
    

    如果你的流在达到状态后是“冷的”,当条件不满足时,如果没有更多的观察者,流将完全停止发射。否则,它将继续(例如,如果您使用共享流),但您当前的观察者(您应用于问题)将停止收集。

    作为针对您的特定情况的解决方法,如果您的流程不是“冷”,您可以致电

    flowOfInterest.take(1).collect { ... } 
    

    接收谓词返回 false 的元素。

    您还可以使用额外的布尔值,当您达到某些条件时,您将在收集块中修改该值,这将是takeWhile 的谓词。

    【讨论】:

    • 谢谢,但这不适用于我的用例,因为根据文档,“它确实不是包含谓词返回 false 的元素”。
    • @metrik 我扩展了我的答案,也许它有帮助
    猜你喜欢
    • 1970-01-01
    • 2019-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-05
    • 2016-10-27
    • 1970-01-01
    相关资源
    最近更新 更多