Android Compose MutableStateFlow:添加到 Set 不会调用重组

问题描述 投票:0回答:1

类似的问题还有很多,但用法一直在变化;这是我发现的唯一专门讨论 MutableSets、MutableStateFlows 和collectAsState() 的问题。

问题是,当我向 MutableSet 添加元素时,可组合项不会重新组合。 下面的代码是我能得到的最简单的代码,但仍然显示问题。

查看模型

class MyViewmodel : ViewModel() {

    private val _infoSet = MutableStateFlow(mutableSetOf<MyInfo>())
    val infoSet = _infoSet.asStateFlow()

    fun add() {
        val newInfo = MyInfo(num = infoSet.value.size + 1)
        _infoSet.value.add(newInfo)
        Log.d(TAG, "infoSet is now ${infoSet.value}")
    }
}

data class MyInfo(
    val name: String = "foo",
    val num: Int = -1
)

主要活动

class MainActivity : ComponentActivity() {

    private lateinit var myViewmodel: MyViewmodel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        myViewmodel = MyViewmodel()

        setContent {
            val infoSet by myViewmodel.infoSet.collectAsStateWithLifecycle()
            RecompositionTestTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    DrawStuff(
                        modifier = Modifier.padding(innerPadding),
                        infoSet = infoSet,
                        myViewmodel
                    )
                }
            }
        }
    }
}

@Composable
fun DrawStuff(
    modifier: Modifier = Modifier,
    infoSet: Set<MyInfo>,
    viewmodel: MyViewmodel
) {
    Log.d(TAG, "DrawStuff() start")
    LazyColumn(modifier = modifier.safeDrawingPadding()) {
        item { Text("This should display a Set of MyInfo") }
        item {
            Button(
                onClick = {
                    viewmodel.add()
                    Toast.makeText(ctx, "infoSet = $infoSet", Toast.LENGTH_SHORT).show()
                }
            ) { Text("add") }
        }

        // the MyInfo list
        infoSet.forEach { info ->
            Log.d(TAG, "listing the MyInfos $info")
            item {
                Text("${info.name}, ${info.num}")
            }
        }
    }
}

Log语句显示点击按钮调用了MyViewmodel.add()。 但日志也清楚地表明 DrawStuff 没有被调用。 没想到Toast消息正确显示了信息。

我在这里做错了什么吗? 我需要做什么才能让

DrawStuff()
在改变时显示
infoSet
的内容? (注意:我已经做了测试,其中 infoSet 预加载了数据。它绘制正确,但在添加项目时仍然不会改变。)

android kotlin android-jetpack-compose set kotlin-flow
1个回答
3
投票

根据经验,切勿在 StateFlow 中使用可变对象。您使用 MutableSet 作为流的内容。将其替换为不可变的Set,一切都会按预期工作。

原因是 StateFlow 永远不会收到其值内容修改的通知,因此它也无法通知收集器有新值。

在您的视图模型中,只需将

_infoSet
更改为:

private val _infoSet = MutableStateFlow(emptySet<MyInfo>())

现在,您无法再向此集合添加任何新项目,因此

_infoSet.value.add(newInfo)
将无法编译。解决方案是创建一个“新”集(也是不可变的),由旧值和新值组成。这可以很容易地完成,如下所示: _infoSet.update { it + newInfo }

虽然 
_infoSet.value = _infoSet.value + newInfo

乍一看似乎也可以工作,但这实际上可能会导致不一致的更新,因为另一个线程可能会在计算过程中更改

_infoSet.value
。当您需要
old
值来计算 new 值时,更新 MutableStateFlow 的线程安全方法是使用 update 方法,如上所示。
现在 StateFlow 可以在其值发生变化时通知其收集器,因此 Compose 可以触发重组以相应地更新 UI。


编辑:

我刚刚意识到您在创建newInfo时也访问了流的当前值。因此,也必须将其移动到

update
lambda 内部以使其保持一致:
_infoSet.update {
    val newInfo = MyInfo(num = it.size + 1)
    it + newInfo
}

	
© www.soinside.com 2019 - 2024. All rights reserved.