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).
