Há muitas perguntas semelhantes sobre isso, mas os usos estão mudando; esta é a única pergunta que encontrei que fala especificamente sobre MutableSets, MutableStateFlows e collectAsState().
O problema é que quando adiciono um elemento ao meu MutableSet, o composable não recompõe. O código a seguir é o mais simples que posso obter e ainda mostra o problema.
Modelo de visualização
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
)
Atividade principal
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}")
}
}
}
}
As instruções de Log mostram que clicar no botão chama MyViewmodel.add(). Mas os logs também mostram claramente que DrawStuff não é chamado. Inesperadamente, a mensagem Toast mostra corretamente as informações.
Há algo que estou fazendo errado aqui? O que preciso fazer para exibir DrawStuff()
o conteúdo de infoSet
quando ele muda? (Nota: Fiz testes onde infoSet foi pré-carregado com dados. Ele desenha corretamente, mas ainda não muda quando itens são adicionados.)
Como regra geral, nunca use um objeto mutável em um StateFlow. Você usa um MutableSet como o conteúdo do fluxo. Substitua-o por um Set imutável e tudo funcionará conforme o esperado.
O motivo para isso é que o StateFlow nunca é notificado sobre a modificação do conteúdo do seu valor, portanto, ele também não pode notificar o coletor de que há um novo valor.
No seu modelo de visualização, basta alterar
_infoSet
para isto:Agora, você não pode mais adicionar novos itens a este conjunto, então
_infoSet.value.add(newInfo)
ele não será compilado. A solução é criar um novo conjunto (também imutável), consistindo dos valores antigos mais o novo valor. Isso pode ser feito facilmente assim:Embora
_infoSet.value = _infoSet.value + newInfo
também pareça funcionar à primeira vista, isso pode realmente levar a atualizações inconsistentes porque outro thread pode mudar_infoSet.value
durante o cálculo. A maneira thread-safe de atualizar um MutableStateFlow quando você precisa do valor antigo para calcular o novo valor é usar oupdate
método conforme mostrado acima.Agora o StateFlow pode notificar seus coletores quando seu valor muda, então o Compose pode acionar uma recomposição para atualizar a IU adequadamente.
Editar: Acabei de perceber que você acessa o valor atual do fluxo na criação de
newInfo
também. Então, isso também deve ser movido para dentro doupdate
lambda para torná-lo consistente: