【问题标题】:DiffUtil redraw all items in ListAdapter KotlinDiffUtil 重绘 ListAdapter Kotlin 中的所有项目
【发布时间】:2023-04-02 14:40:01
【问题描述】:

我在 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 每次都会检查该值是否为空。

标签: android kotlin android-recyclerview android-adapter android-diffutils


【解决方案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
猜你喜欢
  • 2011-06-08
  • 2020-01-12
  • 1970-01-01
  • 2019-08-11
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 2020-05-14
  • 2020-06-04
相关资源
最近更新 更多