Search code examples
androidandroid-recyclerviewandroid-databindingandroid-jetpack

Android: Updating RecyclerView list with Two-Way Data Binding


I am using two way data binding with my RecyclerView for the first time and I am confused on how to send the list of values to the RecyclerView adapter from my ViewModel

Usually I would go to my Activity/Fragment and do a call like this to refresh the RecyclerView list:

viewModel.getList().observe(viewLifecycleOwner, Observer { list ->
              recyclerView.adapter = adapter(list)
          })

But since everything is being done in the ViewModel now and skipping over the Activity/Fragment, what would be the best way to send the list over?

ViewModel:

class ViewModel : ViewModel() {

    // This variable is binded to the layout so it has the value of the EditText
    val editTextContent = MutableLiveData<String>()

    // This function is also binded to the layout view
    fun buttonClick() {
        //Here it returns a list that I would send to the adapter
        getList(editTextContent.value.toString())
        // How else can I do this?
    }

    fun getList(search: String): MutableLiveData<List<Object>> = Repository.getList(search)
}

Layout XML:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewmodel"
            type="com.example.app.ui.search.FragmentViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".ui.search.Fragment">

        <EditText
            android:id="@+id/edit_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={viewmodel.editTextContent}"
            android:hint="Name" />

        <Button
            android:id="@+id/search_fragment_search_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Search"
            android:onClick="@{() -> viewmodel.buttonClick()}"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/fragment_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>
</layout>

Solution

  • I use custom attribute to do that.
    In viewModel create a mutableLiveData to hold your list

        var list = MutableLiveData<List<Object>>()
    
    

    Change your getList Function to update list mutableLiveData

        fun getList(search: String): MutableLiveData<List<Object>>{
           list.postValue(Repository.getList(search))
        }
    

    I create class binding adapter. In BindingAdapter.kt

        @BindingAdapter("listData")
        fun bindRecyclerView(recyclerView: RecyclerView, data: List<Object>) {
            val adapter = recyclerView.adapter as CountryAdapter
            recyclerView.adapter = Adapter(data)
        }
    
    

    In Adapter class, in viewholder, you should call executePendingBindings

        binding.executePendingBindings()
    
    

    In XML layout

         <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/fragment_recycler_view"
                    android:layout_width="match_parent"
                    listData="@{mainViewModel.list}"
                    android:layout_height="match_parent" />