New to Android development and I’m trying to wrap my head around two-way data binding in conjunction with RecyclerView, ViewModel, Room and LiveData. I grok one-way bindings, but can’t figure out two-way.
Simply, I’d like to be able to tap the id/switch_enabled Switch and update the Db to reflect this (I then plan to leverage this to update other members in the class/Db). I think I need some help with set(value) on my ViewModel and getting the correct RecyclerView item updated in the Db, but I’m uncertain how to do this or if this is the right or best way to do this.
Thank you.
Class:
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
}
Layout detail for 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>
ViewModel:
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()
}
}
Fragment:
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
}
List Adapter:
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))
}
}
}