首页 > 解决方案 > 双向数据绑定、RecyclerView、ViewModel、Room、LiveData、Oh My

问题描述

刚接触 Android 开发,我正在尝试结合 RecyclerView、ViewModel、Room 和 LiveData 进行双向数据绑定。我摸索单向绑定,但无法弄清楚双向。

简单地说,我希望能够点击 id/switch_enabled 开关并更新 Db 以反映这一点(然后我计划利用它来更新类/Db 中的其他成员)。我想我需要一些帮助来处理我的 ViewModel 上的 set(value) 并在 Db 中更新正确的 RecyclerView 项目,但我不确定如何执行此操作,或者这是否是正确或最佳的方法。

谢谢你。

班级:

data class Person (@ColumnInfo(name = "first_name") val firstName: String,
                   @ColumnInfo(name = "last_name") val lastName: String,

                   //...

                   val enabled: Boolean = true
){
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}

RecyclerView 的布局细节:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="p" type="com.example.data.Person" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/constraintLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/first_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{p.firstName}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="John" />

        <TextView
            android:id="@+id/last_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:text="@{' ' + p.lastName}"
            app:layout_constraintStart_toEndOf="@id/first_name"
            app:layout_constraintTop_toTopOf="parent"
            tools:text=" Doubtfire" />

        <Switch
            android:id="@+id/switch_enabled"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="@={p.enabled}"
            app:layout_constraintBaseline_toBaselineOf="@id/last_name"
            app:layout_constraintEnd_toEndOf="parent" />

        <!--...-->

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

视图模型:

class MainViewModel(private val repository: DataRepository) : ViewModel() {
    private val _people: LiveData<List<Person>>
//    @Bindable?
//    @get:Bindable?
    var people: LiveData<List<Person>>
        @Bindable
        get() = _people
        set(value) {
            //Find out which member of the class is being changed and update the Db?
            Log.d(TAG, "Value for set is $value!")
        }
    init {
        _people = repository.livePeople()
    }
}

分段:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    val binding = FragmentPeopleBinding.inflate(inflater, container, false)
    val context = context ?: return binding.root

    val factory = Utilities.provideMainViewModelFactory(context)
    viewModel = ViewModelProviders.of(requireActivity(), factory).get(MainViewModel::class.java)

    val adapter = PeopleViewAdapter()
    viewModel.people.observe(this, Observer<List<Person>> {
        adapter.submitList(it)
    })

    binding.apply {
        vm = viewModel
        setLifecycleOwner(this@PeopleFragment)
        executePendingBindings()
        rvPeopleDetails.adapter = adapter
    }
    return binding.root
}

列表适配器:

class PeopleViewAdapter: ListAdapter<Person, PeopleViewAdapter.ViewHolder>(PeopleDiffCallback()) {
    class PeopleDiffCallback : DiffUtil.ItemCallback<Person>() {
        override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean     {
            return oldItem.number == newItem.number
        }
    }

    class ViewHolder(val binding: FragmentPeopleDetailBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(person: Person) {
            binding.p = person
        }
    }

    @NonNull
    override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder =
            ViewHolder(FragmentPeopleDetailBinding.inflate(LayoutInflater.from(parent.context), parent, false))

    @NonNull
    override fun onBindViewHolder(@NonNull holder: ViewHolder, position: Int) {
        holder.apply {
            bind(getItem(position))
        }
    }
}

标签: android-recyclerviewandroid-roomandroid-databindingandroid-livedataandroid-viewmodel

解决方案


我刚刚遇到了在具有 ViewModel 和 RecyclerView 列表的 MVVM 架构中设置两种方式数据绑定的相同问题。我确定在这种情况下实现双向绑定是不可能或不值得努力的,因为您没有直接在 recyclerview 项目布局中使用视图模型(您使用的布局变量是 Person 类型,而不是你的视图模型)。

我建议实际上将您的视图模型添加为布局变量,然后android:onClick="@{() -> viewmodel.onSwitchClicked()}"在您的视图模型中使用和实现该方法。

在此处查看我的项目中的详细信息:https ://github.com/linucksrox/ReminderList


推荐阅读