Combining data binding with Lifecycle components

We can use data binding along with Lifecycle components (e.g. ViewModel and LiveData) to remove the controller (Activity or Fragment) as middle-man from the process of passing data from the ViewModel to the View.

Without data binding (at least, only using data binding to avoid findViewById):

import ...

class MyFragment : Fragment() {

    private lateinit var viewModel: MyViewModel // Declare a MyViewModel object

    private lateinit var binding: MyFragmentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Inflate view and obtain an instance of the binding class.
        val binding: ScoreFragmentBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.my_fragment,
                container,
                false
        )

        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java) // Initialise the MyViewModel object to a MyViewModel instance
        viewModel.myVar.observe(this, Observer { myVar -> // Observe a variable in the ViewModel class
            binding.myTextview.text = myVar.toString() // Use it to change our UI
        })

        binding.myButton.setOnClickListener { viewModel.changeVar() } // In this example, we have a button with an onClickListener which calls a MyViewModel function to change the variable

        return binding.root
    }
}

With data binding:

<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> // Add a data tag
        <variable // With a variable
            name="myViewModel" // Give it a name
            type="com.example.android.myapp.MyViewModel" /> // Point to the ViewModel class
    </data>

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

        <TextView
            android:id="@+id/my_textview"
            android:text="@{@string/string_format(myViewModel.myVar)}" // myVar is a variable within the ViewModel with a public getter. Don't need myVar.value thanks to data binding. Defaults to empty String. This has been wrapped in a String format defined in strings.xml ( <string name="string_format">\"%s\"</string> ) which wraps the String in quotes. Customise this for other types e.g. Ints, or use String.valueOf()
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{() -> myViewModel.changeVar()}" /> // Add an onClick to provide a listener for the button

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

And our Fragment looks like:

import ...

class MyFragment : Fragment() {

    private lateinit var viewModel: MyViewModel // Declare a MyViewModel object

    private lateinit var binding: MyFragmentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Inflate view and obtain an instance of the binding class.
        val binding: ScoreFragmentBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.my_fragment,
                container,
                false
        )

        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
        // We have removed our onClickListener and Observer

        binding.myViewModel = viewModel // Link your ViewModel with the data tag in the xml file. Allows 

        binding.setLifecycleOwner(this) // Set the Lifecycleowner of the bindingto the current Fragment. Allows us to automatically update layouts

        return binding.root
    }
}

For anything more complicated than plain text we meed to move the logic and data manipulation to the ViewModel and use the likesĀ  of Transformations.map (see this post).