Jetpack Compose – LazyColumn 不重组
Posted
技术标签:
【中文标题】Jetpack Compose – LazyColumn 不重组【英文标题】:Jetpack Compose – LazyColumn not recomposing 【发布时间】:2021-06-01 13:08:10 【问题描述】:我的 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
pups 正在生成更新的值,正如您在我的 logcat 中看到的那样
我已将打印语句放在我的可组合项上,但在流程发出新值后它们不会重新组合。
编辑。
看起来 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
【问题讨论】:
【参考方案1】:
Flow
pups 正在生成更新的值,您可以在我的 logcat 中看到
不完全是。
Flow
发出相同的List
相同的Puppy
对象。我相信 Compose 看到 List
与之前的 List
对象相同,并假设没有任何变化。
我建议的更改:
使Puppy
成为不可变的data
类(即,没有var
属性)
摆脱changeFlow
并让getPuppies()
返回一个稳定的MutableStateFlow<List<Puppy>>
(或者让它成为公共财产)
在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
【讨论】:
感谢@CommonsWare,我还考虑过实际对象仍然是相同的对象但具有修改的属性的想法。在flatMapLatest
上,我尝试通过创建一个新的MutableList
来重新创建列表,用旧对象填充它,然后通过流发出它。所以List
参考不能成为问题。在切换采用的标志后,我将尝试重新创建整个列表(通过 pup.copy 逐个对象)。
而且它起作用了......似乎在 Compose 的运行时深处,他们正在检查引用的相等性而不是调用 equalsTo
?查看我上次编辑的代码
@SomerandomITboy:它应该使用==
,这就是为什么我猜List
是问题所在。就个人而言,我几乎从不在data class
上使用var
,所以我不太清楚为什么 Compose 没有检测到这种变化。【参考方案2】:
这对我有用。
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
请注意,您没有必须使您的数据类不可变,但是,使其不可变将强制您必须制作元素的副本才能更新它。
【讨论】:
以上是关于Jetpack Compose – LazyColumn 不重组的主要内容,如果未能解决你的问题,请参考以下文章
Android Jetpack Compose学习—— Jetpack compose基础布局
Android Jetpack Compose学习—— Jetpack compose基础布局
Android Jetpack Compose学习—— Jetpack compose入门