I am using a ViewModel to store data. The ViewModel has a StateFlow class that list of Users. There is also a donut:Int field.
In Compose, when I click on the Donut-button, the variable "donut" is incremented and the recomposition is successful - the data on the button "Donut" changes immediately.
On the Users buttons, when I click, the data in the User list changes (looked through logcat), but the recomposition does not occur. But if after that I press the Donut-button, then the user data instantly changes on the screen to the changed ones.
My MainViewModel:
class MainViewModel() : ViewModel() {
private var _usersClass = MutableStateFlow(UsersClass())
val usersClass: StateFlow<UsersClass> = _usersClass.asStateFlow()
fun addDonut(int: Int) {
_usersClass.update {
it.copy(
donuts = int
)
}
}
fun addAgeForAll(){
val newList = _usersClass.value.userList
newList.value.forEach(){a->a.addAge()}
_usersClass.update{
it.copy(
userList = newList
)
}
}
}
My UsersClass:
data class UsersClass(
var donuts: Int = 0,
private var _userList: MutableStateFlow<MutableList<User>> = MutableStateFlow(initData.items.toMutableList()),
var userList: StateFlow<List<User>> = _userList.asStateFlow()
)
data class User(
var username: String = "John Doe",
var age: Int = 18
) {
fun addAge() {
age++
}
}
object initData {
var items = arrayListOf(
User(username = "Elvis", age = 18),
User(username = "Sunny", age = 19),
User(username = "Mary", age = 18)
)
}
MainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val mainViewModel = ViewModelProvider(this)[MainViewModel::class.java]
setContent {
MainScreen(
mainViewModel = mainViewModel,
onClickButtonAddDonut = {
mainViewModel.addDonut(mainViewModel.usersClass.value.donuts + 1)
}
)
}
}
}
@Composable
fun MainScreen(
mainViewModel: MainViewModel,
onClickButtonAddDonut: () -> Unit,
) {
val class1 = mainViewModel.usersClass.collectAsState()
Column {
LazyColumn {
itemsIndexed(class1.value.userList.value) { _, item ->
Button(
onClick = { mainViewModel.addAgeForAll() }
) {
Text(text = item.toString())
}
}
}
Button(
modifier = Modifier,
onClick = { onClickButtonAddDonut() },
content = {
Text(text = "donuts= ${class1.value.donuts}")
})
}
}
Why is recomposition not happening? What am I doing wrong? I can't figure it out for a week :( Thx
The issue is here,
fun addAge() {
age++
}
This doesn't notify Compose that anything has changed as it is mutating the value of age
directly.
In general, the data sent through a state flow should be immutable. To do this you can change User
to be,
data class User(
val username: String = "John Doe",
val age: Int = 18
)
and UserClass
to be,
data class UsersClass(
val donuts: Int = 0,
val userList: List<User> = emptyList()
)
Note the use of val
instead of var
.
After these changes, method on the model could be,
fun addAgeForAll(){
val newList = _usersClass.value.userList.map {
it.copy(age = it.age + 1)
}
_usersClass.update{
it.copy(
userList = newList
)
}
}
I recommend you keep the model simple, like this, until you know that an additional flow is required to keep from skipping frames.