Jetpack Compose – LazyColumn 不重组
Jetpack Compose – LazyColumn not recomposing
我的 LazyColumn 没有重组,但值正在更新。
如果我向下滚动列表并向上滚动,我会看到 UI
的正确值
主要活动
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
MyApp()
}
}
}
}
// Start building your app here!
@Composable
fun MyApp(vm: PuppyListViewModel = viewModel()) {
val puppers by vm.pups.collectAsState(emptyList())
Surface(color = MaterialTheme.colors.background) {
Column {
Toolbar()
LazyColumn {
items(puppers) { pup -> PuppyUI(pup, vm::seeDetails, vm::togglePuppyAdoption) }
}
}
}
}
视图模型
class PuppyListViewModel : ViewModel() {
val pups = PuppyRepo.getPuppies().onEach {
println("FlowEmitted: $it")
}
fun togglePuppyAdoption(puppy: Puppy) = viewModelScope.launch {
PuppyRepo.toggleAdoption(puppy.id)
}
fun seeDetails(puppy: Puppy) {
println("seeDetails $puppy")
}
}
模特
internal var IDS = 0L
data class Puppy (
val name: String,
val tagline: String = "",
val race: String,
@DrawableRes val image: Int,
var adopted: Boolean = false,
val id: Long = ++IDS,
)
存储库
object PuppyRepo {
private val changeFlow = MutableStateFlow(0)
private val pups: List<Puppy>
private val puppyImages = listOf(
R.drawable._1,
R.drawable._2,
R.drawable._3,
R.drawable._4,
R.drawable._5,
R.drawable._6,
R.drawable._7,
R.drawable._8,
R.drawable._9,
R.drawable._10,
R.drawable._11,
R.drawable._12,
R.drawable._13,
R.drawable._14,
R.drawable._15,
R.drawable._16,
R.drawable._17,
R.drawable._18,
R.drawable._19,
)
private val puppyNames = listOf(
"Gordie",
"Alice",
"Belle",
"Olivia",
"Bubba",
"Pandora",
"Bailey",
"Nala",
"Rosco",
"Butch",
"Matilda",
"Molly",
"Piper",
"Kelsey",
"Rufus",
"Duke",
"Ozzy"
)
private val puppyTags = listOf(
"doggo",
"doge",
"special dogo",
"wrinkler",
"corgo",
"shoob",
"puggo",
"pupper",
"small dogo",
"big ol dogo",
"woofer",
"floofer",
"yapper",
"pupper",
"good-boye",
"grizlord",
"snip-snap dogo"
)
private val puppyBreeds = listOf(
"Labrador Retriever",
"German Shepard",
"Golden Retriever",
"French Bulldog",
"Bulldog",
"Beagle",
"Poodle",
"Rottweiler",
"German Shorthaired Pointer",
"Yorkshire Terrier",
"Boxer"
)
init {
pups = puppyImages.map { image ->
val name = puppyNames.random()
val tagline = puppyTags.random()
val breed = puppyBreeds.random()
Puppy(name, tagline, breed, image)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
fun getPuppies() = changeFlow.flatMapLatest { flowOf(pups) }
fun getPuppy(puppyId: Long) = flow {
emit(pups.find { it.id == puppyId })
}
suspend fun toggleAdoption(puppyId: Long): Boolean {
val found = getPuppy(puppyId).first()?.toggleAdoption()?.let { true } ?: false
if (found) {
// Trigger a new emission for those that are consuming a Flow from getPuppies
changeFlow.value = changeFlow.value + 1
}
return found
}
private fun Puppy.toggleAdoption() {
adopted = !adopted
}
}
Flow
小狗正在生成更新值,如您在我的 logcat
中所见
我已经在我的可组合项上放置了打印语句,但在流发出新值后它们没有重新组合。
编辑。
Lookslike Compose 会比较对象的引用,因为那些没有改变,即使流发出新值也不会发生重组(也许是 Compose 上的错误?)
更改了 toggle
功能以重新创建列表元素的实例,如下所示,现在可以使用了。
注意: 我已经将 Puppy.adopted
设为 val
而不是 var
suspend fun toggleAdoption(puppyId: Long): Boolean {
var found = false
pups = pups.map {
val isThePuppy = it.id == puppyId
found = found || isThePuppy
if(isThePuppy) it.copy(adopted = !it.adopted) else it.copy()
}
if (found) {
// Trigger a new emission for those that are consuming a Flow from getPuppies
changeFlow.value = changeFlow.value + 1
}
return found
}
The Flow
pups is producing updated values as you can see in my logcat
不完全是。
Flow
发出相同 List
的相同 Puppy
对象。我相信 Compose 看到 List
与以前的 List
对象相同,并假定没有变化。
我建议的更改:
使 Puppy
成为不可变的 data
class(即没有 var
属性)
摆脱 changeFlow
并拥有 getPuppies()
return 一个稳定的 MutableStateFlow<List<Puppy>>
(或者让它只是一个 public 属性)
在 toggleAdoption()
中,创建一个新的 Puppy
对象列表并使用它来更新 MutableStateFlow<List<Puppy>>
:
suspend fun toggleAdoption(puppyId: Long) {
val current = puppies.value // assumes that puppies is a MutableSharedFlow<List<Puppy>>
val replacement = current.map { if (it.id == puppyId) it.copy(adopted = !it.adopted) else it }
puppies.value = replacement
}
这对我有用。
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
var selectables: List<Selectable> by mutableStateOf(List(100) { Selectable(name = "$it") })
private set
fun onTapped(tappedItem: Selectable) {
val index = selectables.indexOf(tappedItem)
selectables = selectables.toMutableList().also {
it[index] = tappedItem.copy(selected = !tappedItem.selected)
}
}
}
data class Selectable(
val name: String,
var selected: Boolean = false,
)
关键部分是:
- 重新分配列表而不是就地修改它(例如,将
selectables
设为 MutableList
并执行 selectables[index] = tappedItem.copy(selected = !tappedItem.selected)
是行不通的)
- 重新分配所选项目而不是就地修改它,例如以下行不通
selectables = selectables.toMutableList().also {
it[index].selected = !tappedItem.selected
}
请注意,您 无法使数据 class 不可变,但是,使其不可变将强制您必须制作元素的副本为了更新它。
我的 LazyColumn 没有重组,但值正在更新。
如果我向下滚动列表并向上滚动,我会看到 UI
的正确值主要活动
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
MyApp()
}
}
}
}
// Start building your app here!
@Composable
fun MyApp(vm: PuppyListViewModel = viewModel()) {
val puppers by vm.pups.collectAsState(emptyList())
Surface(color = MaterialTheme.colors.background) {
Column {
Toolbar()
LazyColumn {
items(puppers) { pup -> PuppyUI(pup, vm::seeDetails, vm::togglePuppyAdoption) }
}
}
}
}
视图模型
class PuppyListViewModel : ViewModel() {
val pups = PuppyRepo.getPuppies().onEach {
println("FlowEmitted: $it")
}
fun togglePuppyAdoption(puppy: Puppy) = viewModelScope.launch {
PuppyRepo.toggleAdoption(puppy.id)
}
fun seeDetails(puppy: Puppy) {
println("seeDetails $puppy")
}
}
模特
internal var IDS = 0L
data class Puppy (
val name: String,
val tagline: String = "",
val race: String,
@DrawableRes val image: Int,
var adopted: Boolean = false,
val id: Long = ++IDS,
)
存储库
object PuppyRepo {
private val changeFlow = MutableStateFlow(0)
private val pups: List<Puppy>
private val puppyImages = listOf(
R.drawable._1,
R.drawable._2,
R.drawable._3,
R.drawable._4,
R.drawable._5,
R.drawable._6,
R.drawable._7,
R.drawable._8,
R.drawable._9,
R.drawable._10,
R.drawable._11,
R.drawable._12,
R.drawable._13,
R.drawable._14,
R.drawable._15,
R.drawable._16,
R.drawable._17,
R.drawable._18,
R.drawable._19,
)
private val puppyNames = listOf(
"Gordie",
"Alice",
"Belle",
"Olivia",
"Bubba",
"Pandora",
"Bailey",
"Nala",
"Rosco",
"Butch",
"Matilda",
"Molly",
"Piper",
"Kelsey",
"Rufus",
"Duke",
"Ozzy"
)
private val puppyTags = listOf(
"doggo",
"doge",
"special dogo",
"wrinkler",
"corgo",
"shoob",
"puggo",
"pupper",
"small dogo",
"big ol dogo",
"woofer",
"floofer",
"yapper",
"pupper",
"good-boye",
"grizlord",
"snip-snap dogo"
)
private val puppyBreeds = listOf(
"Labrador Retriever",
"German Shepard",
"Golden Retriever",
"French Bulldog",
"Bulldog",
"Beagle",
"Poodle",
"Rottweiler",
"German Shorthaired Pointer",
"Yorkshire Terrier",
"Boxer"
)
init {
pups = puppyImages.map { image ->
val name = puppyNames.random()
val tagline = puppyTags.random()
val breed = puppyBreeds.random()
Puppy(name, tagline, breed, image)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
fun getPuppies() = changeFlow.flatMapLatest { flowOf(pups) }
fun getPuppy(puppyId: Long) = flow {
emit(pups.find { it.id == puppyId })
}
suspend fun toggleAdoption(puppyId: Long): Boolean {
val found = getPuppy(puppyId).first()?.toggleAdoption()?.let { true } ?: false
if (found) {
// Trigger a new emission for those that are consuming a Flow from getPuppies
changeFlow.value = changeFlow.value + 1
}
return found
}
private fun Puppy.toggleAdoption() {
adopted = !adopted
}
}
Flow
小狗正在生成更新值,如您在我的 logcat
我已经在我的可组合项上放置了打印语句,但在流发出新值后它们没有重新组合。
编辑。
Lookslike Compose 会比较对象的引用,因为那些没有改变,即使流发出新值也不会发生重组(也许是 Compose 上的错误?)
更改了 toggle
功能以重新创建列表元素的实例,如下所示,现在可以使用了。
注意: 我已经将 Puppy.adopted
设为 val
而不是 var
suspend fun toggleAdoption(puppyId: Long): Boolean {
var found = false
pups = pups.map {
val isThePuppy = it.id == puppyId
found = found || isThePuppy
if(isThePuppy) it.copy(adopted = !it.adopted) else it.copy()
}
if (found) {
// Trigger a new emission for those that are consuming a Flow from getPuppies
changeFlow.value = changeFlow.value + 1
}
return found
}
The
Flow
pups is producing updated values as you can see in my logcat
不完全是。
Flow
发出相同 List
的相同 Puppy
对象。我相信 Compose 看到 List
与以前的 List
对象相同,并假定没有变化。
我建议的更改:
使
Puppy
成为不可变的data
class(即没有var
属性)摆脱
changeFlow
并拥有getPuppies()
return 一个稳定的MutableStateFlow<List<Puppy>>
(或者让它只是一个 public 属性)在
toggleAdoption()
中,创建一个新的Puppy
对象列表并使用它来更新MutableStateFlow<List<Puppy>>
:
suspend fun toggleAdoption(puppyId: Long) {
val current = puppies.value // assumes that puppies is a MutableSharedFlow<List<Puppy>>
val replacement = current.map { if (it.id == puppyId) it.copy(adopted = !it.adopted) else it }
puppies.value = replacement
}
这对我有用。
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
var selectables: List<Selectable> by mutableStateOf(List(100) { Selectable(name = "$it") })
private set
fun onTapped(tappedItem: Selectable) {
val index = selectables.indexOf(tappedItem)
selectables = selectables.toMutableList().also {
it[index] = tappedItem.copy(selected = !tappedItem.selected)
}
}
}
data class Selectable(
val name: String,
var selected: Boolean = false,
)
关键部分是:
- 重新分配列表而不是就地修改它(例如,将
selectables
设为MutableList
并执行selectables[index] = tappedItem.copy(selected = !tappedItem.selected)
是行不通的) - 重新分配所选项目而不是就地修改它,例如以下行不通
selectables = selectables.toMutableList().also {
it[index].selected = !tappedItem.selected
}
请注意,您 无法使数据 class 不可变,但是,使其不可变将强制您必须制作元素的副本为了更新它。