【问题标题】:Build a list from kotlin flow从 kotlin flow 构建一个列表
【发布时间】:2021-07-23 12:50:42
【问题描述】:

我正在尝试构建一个使用 kotlin 从数据层到视图的流的应用程序,但在解决像这样的简单问题时遇到了很多困难。

我正在从来自我的数据层的 StateFlow 中收集整数值,并希望在将其共享给我的 viewmodel 并通过另一个 StateFlow 进行查看之前建立一个列表。

示例代码:

class MyDataSource {
    val inputFlow: StateFlow<Int> = TODO()
}

class MyService(
    dataSource: MyDataSource
) {
    private val intList: MutableList<Int> = ArrayList()

    private val _intFlow = MutableStateFlow<List<Int>>(listOf())
    val intFlow: StateFlow<List<Int>> = _intFlow

    init {
        GlobalScope.launch(Dispatchers.IO) {
            dataSource.inputFlow.collect {
                if (!intList.contains(it))
                    intList.add(it)
                
                _intFlow.emit(ArrayList(intList))
            }
        }
    }
}

难道没有更好的方法吗?就像我错过的流量运算符?这似乎特别难看,因为我每次都需要构建一个新的 ArrayList,否则即使列表的内容发生了变化,流程也不会发送新值,因为它仍然是同一个列表。

【问题讨论】:

  • 嗨,你在关注 MVVM 吗?您的服务类代码不是 viewModel,您在寻找示例吗?
  • @ZaidZakir 它可能是 ViewModel 的一部分,但我选择将其外部化到另一个我称之为“服务”的层(类似于某些应用程序有一个“存储库”层,但我的应用程序更多“业务逻辑繁重”并减少对外部 API 的使用)。但我不明白这与我的问题有什么关系。

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


【解决方案1】:

您可以做一些清理工作,但我认为流动不断增长的列表的安排过于具体,以至于核心库中没有任何一体化解决方案。

  1. 你应该avoid using GlobalScope。这个服务类应该更恰当地拥有自己的范围,以便您可以在需要时管理生命周期。

  2. 您的支持列表应该是一个集合,因为您实际上是在将其用作性能较差的集合,方法是在添加之前检查对象是否在其中。

  3. 使用链式操作符和stateIn 比操作 MutableStateFlow 并不得不使用支持属性更简洁。

class MyService(
    dataSource: MyDataSource
) {
    private val intSet = mutableSetOf<Int>()
    private val scope = CoroutineScope(Dispatchers.Default)

    val intFlow = dataSource.inputFlow
        .map {
            intSet += it
            intSet.toList()
        }.stateIn(scope, SharingStarted.Eagerly, emptyList())
}

编辑:这是在每次迭代中避免设置到列表副本和新分配的未经测试的尝试:

class MyService(
    dataSource: MyDataSource
) {
    private val intSet = mutableSetOf<Int>()
    private var currentList = mutableListOf<Int>()
    private var previousList = mutableListOf<Int>()
    private var lastEmittedValue: Int? = null
    private val scope = CoroutineScope(Dispatchers.Default)

    val intFlow = dataSource.inputFlow
        .mapNotNull { newValue ->
            if (!intSet.add(newValue)) 
                return@mapNotNull null
            lastEmittedValue?.let {
                previousList += it
            } // both backing lists now identical
            lastEmittedValue = newValue
            previousList = currentList.also { currentList = previousList }
            // previousList is the one missing lastEmittedValue on next iteration:
            currentList += newValue
            currentList
        }.stateIn(scope, SharingStarted.Eagerly, emptyList())
}

【讨论】:

  • 这正是我想要的。唯一困扰我的是您仍然每次使用 toList() 重新创建一个新列表,从长远来看,该资源不是很重吗? (我大大简化了我的代码,实际上我操作了一个复杂对象列表,我可以在列表中添加、删除或编辑这些对象)。
  • StateFlow 类必须有一种方法来区分发射的对象。它不会连续发出两个等效的对象,因此如果连续的两个对象是同一个实例,则无法比较它们。我想你可以通过拥有两个支持列表并在它们之间交替来破解它,但这对我来说听起来很脆弱,因为要让它们都与支持数据保持正确是多么棘手。您需要另一个属性来保留仅在一个列表中的最新值,并且需要在下一个值到达后添加到另一个列表中。
  • 请注意,您已经在原始代码的每次迭代中将备用列表复制到一个新的 List 实例。底层对象的复杂性与列表副本的性能无关,因为 Set/List 只包含对对象的引用。
【解决方案2】:

scan 能满足你的需要吗?

来自文档:

用操作折叠给定的流,发出每个中间结果,包括初始值。 [...] 例如:

flowOf(1, 2, 3).scan(emptyList&lt;Int&gt;()) { acc, value -&gt; acc + value }.toList() 将产生[], [1], [1, 2], [1, 2, 3]]

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/scan.html

【讨论】:

    猜你喜欢
    • 2022-07-29
    • 1970-01-01
    • 2018-01-31
    • 1970-01-01
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    相关资源
    最近更新 更多