如何在 MVVM 架构中观察 RecyclerView 适配器中的 LiveData?

Posted

技术标签:

【中文标题】如何在 MVVM 架构中观察 RecyclerView 适配器中的 LiveData?【英文标题】:How to observe LiveData in RecyclerView adapter in MVVM architecture? 【发布时间】:2019-06-04 05:01:30 【问题描述】:

我有一个RecyclerView 适配器和它的项目中的一个按钮。

当我点击按钮时,我想从服务器中删除它的项目,然后从RecyclerView 中删除。

我想通过观察LiveData 来做到这一点(当它从服务器中删除时,我必须将其从回收站视图中删除,因此我需要服务器的结果)

最佳实践方法是什么 - 我必须在片段中观察并将侦听器传递给适配器并在片段中实现它,当用户单击按钮时调用片段中的方法或有更好的方法这样做?

【问题讨论】:

观察片段中的数据并将其传递给适配器或通知给适配器。 将 LifecycleOwner 对象传递给适配器? 【参考方案1】:

我认为使用 notifyDataSetChanged 是可以的。 我的 ViewModel 中有 LiveData 公司。我可以像这样在 onCreateView 片段中观察到它

 viewModel.companies.observe(viewLifecycleOwner, 
        binding.searchResultsRecyclerView.adapter?.notifyDataSetChanged()
    )

当公司发生任何变化时,recyclerView 会更新。

【讨论】:

【参考方案2】:

我知道现在回答为时已晚。 但我希望它能帮助其他开发人员寻找类似问题的解决方案。

看看LiveAdapter。

你只需要在 Gradle 中添加最新的依赖即可。

dependencies 
    implementation 'com.github.RaviKoradiya:LiveAdapter:1.3.2-1608532016'
    // kapt 'com.android.databinding:compiler:GRADLE_PLUGIN_VERSION' // this line only for Kotlin projects

并将适配器与您的 RecyclerView 绑定

LiveAdapter(
            data = liveListOfItems,
            lifecycleOwner = this@MainActivity,
            variable = BR.item )
            .map<Header, ItemHeaderBinding>(R.layout.item_header) 
                areContentsTheSame  old: Header, new: Header ->
                    return@areContentsTheSame old.text == new.text
                
            
            .map<Point, ItemPointBinding>(R.layout.item_point) 
                areContentsTheSame  old: Point, new: Point ->
                    return@areContentsTheSame old.id == new.id
                
            
            .into(recyclerview)

就是这样。适配器实现无需额外编写代码,观察LiveData并通知适配器。

【讨论】:

【参考方案3】:

经过几篇文章的全面搜索,我终于找到了推荐的解决方案。 第 1 步:在您的适配器中声明一个接口,如下所示:

class AddExpenseLabelAdapter(
    val items: List<LabelResponse>, 
    val context: Context, 
    val listener: OnLabelClickListener
) : RecyclerView.Adapter<AddExpenseLabelAdapter.ViewHolder>() 

    interface OnLabelClickListener 
        fun onLabelDeleteButtonClicked(request : SubCategoryLabelRequest)
    

    lateinit var binding: ItemListExpenseAddLabelBinding

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder 
        val inflater = LayoutInflater.from(context)
        val binding = ItemListExpenseAddLabelBinding.inflate(inflater)
        this.binding = binding
        return ViewHolder(binding)
    

    override fun onBindViewHolder(holder: ViewHolder, position: Int) 
        holder.bind(items[position])
    

    override fun getItemCount(): Int = items.size

    inner class ViewHolder(val binding: ItemListExpenseAddLabelBinding) : RecyclerView.ViewHolder(binding.root), OnClickListener 
        lateinit var item: LabelResponse
        fun bind(item: LabelResponse) 
            this.item = item
            binding.itemListLabelLayout.setBackgroundColor(Color.parseColor("#" + item.color))
            binding.labelResponse = item
            binding.onClickListener = this
            binding.executePendingBindings()
        

        override fun onClick(view: View) 
            if (view.id == binding.itemListLabelLayout.id) 
                val subCategoryLabelRequest = SubCategoryLabelRequest(item.id)
                listener.onLabelDeleteButtonClicked(subCategoryLabelRequest)
            
        
    

第 2 步:在视图中实现接口并将其传递给适配器,如下所示:

class AddExpenseLabelDialog : DialogFragment(), AddExpenseLabelAdapter.OnLabelClickListener 

    lateinit var binding: DialogAddExpenseLabelBinding

    lateinit var view: Any

    var expenseId: Int = 0
    var categoryId: Int = 0

    lateinit var application: MyApplication

    lateinit var addExpenseLabelViewModel: AddExpenseLabelViewModel

    fun newInstance(expenseId: Int, categoryId: Int): AddExpenseLabelDialog = 
        AddExpenseLabelDialog().also  fragment ->
            arguments = Bundle().also  bundle ->
                bundle.putInt("expenseId", expenseId)
                bundle.putInt("categoryId", categoryId)
            
        

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? 
        binding = DataBindingUtil.inflate(layoutInflater, R.layout.dialog_add_expense_label, container, false)
        addExpenseLabelViewModel = ViewModelProviders.of(this).get(AddExpenseLabelViewModel::class.java)
        expenseId = arguments!!.getInt("expenseId")
        categoryId = arguments!!.getInt("categoryId")
        initialize()
        view = binding.root
        return view as View
    

    fun initialize() 

        binding.labelRec.layoutManager = LinearLayoutManager(context)
        addExpenseLabelViewModel.liveData.observe(this, Observer  response ->
            binding.labelRec.adapter = AddExpenseLabelAdapter(response as ArrayList<LabelResponse>, context!!, this)
        )
    

    override fun onLabelDeleteButtonClicked(request : SubCategoryLabelRequest) 
        addExpenseLabelViewModel.createExpenseLabel(categoryId, expenseId, request).observe(this, Observer  response ->
            when (response?.status) 
                Status.LOADING -> Toast.makeText(activity, "LOADING", Toast.LENGTH_SHORT).show()
                Status.SUCCESS -> 
                    dismiss()
                    Toast.makeText(activity, "SUCCESS", Toast.LENGTH_SHORT).show()
                
                else -> Toast.makeText(activity, InjectorUtil.convertCodeToMessage(response?.error?.code!!), Toast.LENGTH_SHORT).show()
            
        )
    

【讨论】:

为什么每次观察数据时都要创建适配器实例!

以上是关于如何在 MVVM 架构中观察 RecyclerView 适配器中的 LiveData?的主要内容,如果未能解决你的问题,请参考以下文章

如何在MVVM架构中使用RxSwift发送参数来查看模型?

在 MVVM 架构中格式化来自改造 API 的嵌套 JSON 响应 - Kotlin

[架构]MVC/MCP/MVVM三个框架模式

SwiftUI-MVVM

如何在以下 mvvm 架构中使用 @Binding Wrapper?

如何使用 MVVM 架构实现 Firebase Google SignIn?