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&lt;List&lt;Puppy&gt;&gt;(或者让它成为公共财产)

toggleAdoption() 中,创建一个新的Puppy 对象列表并使用它来更新MutableStateFlow&lt;List&lt;Puppy&gt;&gt;

    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入门

Android Jetpack Compose学习—— Jetpack compose入门

jetpack compose 接收返回参数

什么是Jetpack Compose?带你走进Jetpack Compose~