DiffUtil 重绘 ListAdapter Kotlin 中的所有项目

DiffUtil redraw all items in ListAdapter Kotlin

我在 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() 如果有任何我需要的更改否则我不想重新绘制我的整个列表。有什么建议吗?

屏幕上闪烁的原因是您每次恢复时都在创建一个新的适配器实例,并且您的 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

的“检查”

One thing initializeAdapter() function is needed to call every time because viewmodel.abc is checking every time that value is not null.

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

片段应该对其接收到的数据做出反应,但所述数据的处理和逻辑属于别处(viewModel 和更深入的 use-cases/repos)。

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

更新

Here is a Pull Request 在我投入几分钟的示例项目中。当我 运行 这样做时,我在 RecyclerView 的屏幕上看到了两个模拟项目。

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

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

您的适配器似乎依赖于来自 Abc class 的某些信息才能显示列表。然后我会将两者组合成一个数据 class,并将它们一起发布在 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()
    }
}