this code runs on desktop using compose multiplatform but I guess it would work on android the same way.
class MyData{
var x: Int = -1
}
object Repo {
val allData = (0..10).map { MyData().apply { x=it } }
val mySharedFlow = MutableSharedFlow<List<MyData>>(1)
}
fun main() = runBlocking {
// initialize with some data from `Repo.allData`
Repo.mySharedFlow.emit(Repo.allData.take(5))
application {
Window(onCloseRequest = ::exitApplication) {
MainScreen()
}
}
}
@Composable
fun MainScreen() {
val currlist = Repo.mySharedFlow.collectAsState(emptyList())
val scope = rememberCoroutineScope()
Column {
currlist.value.forEach { Text(it.x.toString()) }
Button({
scope.launch {
val newList = currlist.value.toMutableList()
newList[0].x = 500 // changes the original `allData`
// newList[0] = newList[0].apply { x=500 } // changes the original `allData`
// newList[0] = MyData().apply { x=500 } // Does not change the original `allData`
Repo.mySharedFlow.emit(newList.plus(MyData())) //optional: added new element just to force recomposition
}
}) { Text("modify list") }
// A button to print `allData`
Button({ scope.launch { println(Repo.allData.map { it.x }) } }) { Text("print allData") }
}
}
as you can see when I click "modify list" button I'm trying to modify an item in the composable state currlist
but when I check the original list Repo.allData
I find that it changed as well !! why ? I also noticed that using newList[0] = MyData().apply { x=500 }
doesn't cause Repo.allData
to change ! , this is weird for me, I thought that when I initialized the flow with Repo.mySharedFlow.emit(Repo.allData.take(5))
I was creating a completely new list indepedent from Repo.allData
, I hope I formulated the issue correctly, can some one explain to me why allData
changes in the first place and then why it didn't change when using newList[0] = MyData().apply { x=500 }
?
Edit: explained by @Tenfour04 , thanks, it had nothing to do with flows:
println(Repo.allData.map { it.x })
//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
val mlist = Repo.allData.take(5).toMutableList()
mlist[0].x=500
println(Repo.allData.map { it.x })
//[500, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
CodePudding user response:
Although you copied the list using toMutableList()
, it wasn’t a deep copy. Both old and new lists are referencing (pointing at) the same item instances. You are mutating one of those shared item instances.
When you call the constructor you put a new item instance in the new list, so you are not mutating a shared item instance.
Side note, you should avoid ever putting a mutable class (one with any var
properties) in a StateFlow. That is very error prone. StateFlow is always comparing old and new values when it gets new values, so it will get messed up if the old value is getting modified out from under it.