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.observe
在viewmodel.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
属性,并且可以公开单独的Abc
和List<Xyz>
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