DiffUtil 重绘 ListAdapter Kotlin 中的所有项目

Posted

技术标签:

【中文标题】DiffUtil 重绘 ListAdapter Kotlin 中的所有项目【英文标题】:DiffUtil redraw all items in ListAdapter Kotlin 【发布时间】:2021-12-24 09:03:05 【问题描述】:

我在 android Kotlin 中使用 DiffUtil 和 ListAdapter。我在 onResume 方法中从服务器调用数据。当 onResume 调用每个项目时,整个数据正在重绘视图。如果服务器端有任何数据更改,我想更新视图,因此它将反映在应用程序中。

ListActivity.kt

class ListActivity : BaseActivity() 

    lateinit var binding: ListActivityLayoutBinding
    private val viewModel: ListViewModel by inject()
    private var listAdapter: listAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setupViewModel()
        binding = ListActivityLayoutBinding.inflate(layoutInflater)
        setContentView(binding.root)
    

    private fun setupViewModel() 
        viewModel.liveData.observe(this,  list ->
            setupAdapter(list)
        )
    

    private fun setupAdapter(list: List<XYZ>) 
        initializeAdapter()
        listAdapter?.submitList(list)
        binding.recyclerView.adapter = listAdapter
    

    private fun initializeAdapter() 
        viewModel.abc?.let  abc ->
            listAdapter = ListAdapter(abc, object : Listener<XYZ> 
                override fun selectedItem(item: XYZ) 
                    // calling 
                    
                
            )
         ?: run 
            Log.e("Error", "Error for fetching data")
        
    

    override fun onResume() 
        super.onResume()
        viewModel.fetchData()
    

XYZ.kt

data class XYZ(
    val id: String? = null,
    val title: String? = null,
    val count: Int? = null,
    val status: String? = null,
    val item: Qqq? = null
)

QQQ.kt

data class Qqq(
    val id: String? = null,
    val rr: Rr? = null
)

Rr.kt

data class Rr(
    val firstName: String? = null,
    val lastName: String? = null,
)

ListAdapter.kt

class ListAdapter(
    private val abc: Abc,
    private val listener: Listener <XYZ>
) : ListAdapter<XYZ, ListViewHolder>(LIST_COMPARATOR) 

    companion object 
        private val LIST_COMPARATOR = object : DiffUtil.ItemCallback<XYZ>() 
            override fun areItemsTheSame(oldItem: XYZ, newItem: XYZ): Boolean 
                return oldItem.id == newItem.id
            

            override fun areContentsTheSame(oldItem: XYZ, newItem: XYZ): Boolean 
                return ((oldItem.title == newItem.title) && (oldItem.status == newItem.status)
                        && (oldItem.count == newItem.count)
                        && (oldItem.item == newItem.item))
            
        
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder 
        return ListViewHolder.bindView(parent, abc)
    

    override fun onBindViewHolder(holder: ListViewHolder, position: Int) 
        holder.bindItem(getItem(position), listener)
    

ListViewModel.Kt

class ListViewModel : BaseViewModel() 

    var abc: Abc? = null
    private var xyz: List<XYZ>? = null
    var liveData: MutableLiveData<List<XYZ>> = MutableLiveData()

    fun fetchData() 
        viewModelScope.launch 
          
            val firstAsync = async 
                if (abc == null) 
                    abc = getAbc() // First retrofit call
                
            
            val secondAsync = async 
                xyz = getXYZ() // Second retrofit call
            
            firstAsync.await()
            secondAsync.await()
            liveData.postValue(xyz)
        
    

注意,我想检查 abc 在每次调用中是否为空。

1.我的 DiffUitll 回调是否正确?

2. 第一次初始调用我想重绘每个项目,但是,如果我在onResume 中调用viewModel.fetchData(),如果有任何我需要做的更改,否则我不想重画我的整个清单。有什么建议吗?

【问题讨论】:

似乎您在每次通话后都在创建一个新适配器。您应该创建并设置您的适配器 onCreate 并发布一个新列表以计算差异。 @Kdaydin 你能给我一个例子吗?每次都需要调用一件事 initializeAdapter() 函数,因为 viewmodel.abc 每次都会检查该值是否为空。 【参考方案1】:

屏幕上闪烁的原因是因为您每次恢复时都在创建一个新的适配器实例,并且您的 viewModel 订阅被重新触发。

不要让适配器延迟初始化,这没什么好处。

class ListActivity : BaseActivity() 

    lateinit var binding: ListActivityLayoutBinding
    private val viewModel: ListViewModel by inject()

    private val adapterListener = object : Listener<XYZ> 
                override fun selectedItem(item: XYZ)  // TODO  
            
           

    private var listAdapter = ListAdapter(adapterListener)

然后尽可能设置它:

override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setupViewModel()
        binding = ListActivityLayoutBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.yourRecyclerView.adapter = listAdapter
    

然后,一旦您观察到数据并获得数据,请致电listAdapter.submitList(xxx)。除非您真的需要一个全新的适配器(为什么?),否则无需重新创建适配器。

    private fun setupViewModel() 
        viewModel.liveData.observe(this,  list ->
            listAdapter.submitList(list.toMutableList())
        )
    

关于您对abc 的“检查”

每次都需要调用一个 initializeAdapter() 函数,因为 viewmodel.abc 每次都会检查该值是否不为空。

这是 ViewModel 的问题。如果不满足要求,则不应推送新列表。如果list 通过viewmodel.liveData.observeviewmodel.liveData.observe 中提供的list 不应该存在,如果viewmodel.abc 为空,那么您不应该推送实时数据或者应该推送不同的状态。

Fragment 应该对它接收到的数据做出反应,但所述数据和逻辑的处理属于其他地方(viewModel 和更深入的用例/repos)。

你的 Fragment 所做的一切 就是构建框架的东西(RecyclerView 及其附件,如适配器、Layoutmanager,如果需要等)并订阅一个 liveData 流,该流将提供所需的数据将它与框架的东西连接起来。它没有做太多的“思考”,也不应该。

更新

Here is a Pull Request 在我在几分钟内提交的示例项目中。当我运行它时,我在 RecyclerView 的屏幕上看到了两个模拟项目。

【讨论】:

我的适配器没有设置任何东西。 我不知道你的问题可能是什么,我建议调试并放置断点并观察你的代码在做什么。我向你保证 ListAdapters 可以工作。 我将在几分钟内添加项目链接 我正在添加 github link。你能看看吗 感谢您的帮助【参考方案2】:

这个答案完全基于不改变你当前的 ListAdapter 设计。如果您可以修改设计,以便可以随时将回调和 Abc 传递到属性中(因此它有一个空的构造函数),那么一切都会变得更简单。您可以将适配器创建为val 属性,并且可以公开单独的AbcList&lt;Xyz&gt; LiveData,而不必合并它们。

适配器只能创建一次。每次收到新数据时,您都在创建一个新的适配器,这意味着所有旧视图都将被丢弃,新的适配器必须从头开始布局视图。由于您的 Adapter 类只能在 Abc 可用时实例化,因此您应该使用惰性策略来实例化它(仅在它为 null 时创建一个)。

您的适配器似乎依赖于 Abc 类中的一些信息才能显示列表。然后我会将两者组合成一个数据类,并将它们一起发布在 LiveData 中。

看起来您不需要多次检索 Abc,因此您也可以使用惰性策略来检索它。

data class AbcXyzData(abc: Abc, xyzList: List<Xyz>)

class ListViewModel : BaseViewModel() 

    private val mutableLiveData = MutableLiveData<AbcXyzData>()
    val liveData: LiveData<AbcXyzData> get() = mutableLiveData

    fun fetchData() 
        viewModelScope.launch 
            val xyzDeferred = async  getXYZ() 
            val abc = liveData.value?.abc ?: getABC() // assuming getABC() suspends
            mutableLiveData.value = AbcXyZData(abc, xyzDeferred.await())
        
    

class ListActivity : BaseActivity() 

    lateinit var binding: ListActivityLayoutBinding
    private val viewModel: ListViewModel by inject()
    private var listAdapter: ListAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setupViewModel()
        binding = ListActivityLayoutBinding.inflate(layoutInflater)
        setContentView(binding.root)
    

    private fun setupViewModel() 
        viewModel.liveData.observe(this)  (abc, xyzList) ->
            initializeAdapter(abc)
            listAdapter?.submitList(xyzList)
        )
    

    private fun initializeAdapter(abc: Abc) 
        if (listAdapter == null) 
            listAdapter = ListAdapter(abc, object : Listener<XYZ> 
                override fun selectedItem(item: XYZ) 
                    // calling 
                    
                
            )
            binding.recyclerView.adapter = listAdapter
        
    

    override fun onResume() 
        super.onResume()
        viewModel.fetchData()
    

【讨论】:

很好的答案,感谢一百万 我还有一个困惑,你能看看这个issue

以上是关于DiffUtil 重绘 ListAdapter Kotlin 中的所有项目的主要内容,如果未能解决你的问题,请参考以下文章

当视图更新 Android Kotlin 时,DiffUtil 不与 ListAdpater 一起使用

带有DiffUtil的RecyclerView(ListAdapter)阻止UI线程

DiffUtil.ItemCallback - 定义为伴随对象还是类?

在 Recyclerview 中使用 getItemCount 和 ListAdapter

DiffUtil 不刷新观察者调用android kotlin中的视图

在没有唯一标识符的项目上使用“ListAdapter”areItemsTheSame