英文:
Jetpack Compose - have a problem with recomposition
问题
在Compose中,当我点击Donut按钮时,变量"donut"会递增,并且重新组合成功 - 按钮"Donut"上的数据立即更改。
但是在用户按钮上,当我点击时,用户列表中的数据会更改(通过logcat查看),但不会发生重新组合。但是如果在此之后我按下Donut按钮,那么用户数据会立即在屏幕上更改为更改后的数据。
主要问题可能出在以下地方:
-
StateFlow的使用: 您在ViewModel中使用了StateFlow来管理数据状态。当用户按钮点击时,您在
addAgeForAll
函数中更新了userList
的内容,但是这个更新可能没有被Compose框架检测到,因此没有触发重新组合。您可以尝试将userList
的MutableStateFlow更新与StateFlow更新分开,以确保Compose能够正确检测到状态的更改。 -
collectAsState()的使用: 在
MainScreen
中,您使用collectAsState()
来获取usersClass
的值。这意味着MainScreen
将只在usersClass
的值发生实际更改时进行重新组合。如果userList
的内容更改,但userList
本身的引用未更改,那么Compose可能不会触发重新组合。确保在userList
更新时,usersClass
的值也发生更改。 -
按钮点击时的状态更新: 在用户按钮的
onClick
处理程序中,您调用了mainViewModel.addAgeForAll()
,该函数在内部更新了userList
。但是,按钮点击后,class1
的值可能没有直接更改,因此Compose可能不会触发重新组合。您可以尝试在按钮点击处理程序中手动更新class1
的值,以确保重新组合发生。
这些是可能导致重新组合不起作用的一些常见问题。请尝试按照上述建议进行调整,以查看是否可以解决问题。如果问题仍然存在,请提供更多的代码和上下文,以便更具体地帮助您解决问题。
英文:
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
答案1
得分: 4
问题出在这里,
fun addAge() {
age++
}
这不会通知Compose有任何更改,因为它直接修改了age
的值。
一般来说,通过状态流传递的数据应该是不可变的。要做到这一点,您可以将User
更改为,
data class User(
val username: String = "John Doe",
val age: Int = 18
)
以及将UserClass
更改为,
data class UsersClass(
val donuts: Int = 0,
val userList: List<User> = emptyList()
)
请注意使用val
而不是var
。
在这些更改之后,模型上的方法可以是,
fun addAgeForAll(){
val newList = _usersClass.value.userList.map {
it.copy(age = it.age + 1)
}
_usersClass.update{
it.copy(
userList = newList
)
}
}
我建议您保持模型简单,就像这样,直到您知道需要额外的流来防止跳帧。
这是翻译好的部分,不包含代码。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论