ViewModel & LiveData with DataBinding

Integrate ViewModel with DataBinding

This avoids us the need to explicitly declare .setOnClickListener() manually and integrate it into the view.

Up until now, this was our MainActivity following MVVM architecture.

class MainActivity: AppCompatActivity() {

	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		
		viewModel.count.observe(this, Observer {
			binding.countText.text = it.toString()
		})
		
		// we will remove methods such as this one
		binding.button.setOnClickListener {
			viewModel.updateCount()
		}
	}
}

We’re going to connect the ViewModel object with the DataBinding object and declare the method directly inside the .xml.

Open the layout activity_main.xml, for this case, and add <data> tags to assign the view model Object we created there. Be careful as the .xml layouts need to be surrounded by <layout> tags.

<layout>
	<data>
		<variable
		  name="myViewModel"
		  type="es.codes.mario.templates.MainActivityViewModel"/>
	</data>
</layout>

We go to the buttons declaration inside the same .xml file and change the onClick="" variable. This will bind the expression.
(!) To do this, our gradle version has to be higher than 2.0 (!)

<Button 
	...
	android:onClick="@{()->myViewModel.updateCount()}"/>

Now we go back to our class, add the code to bind it, and remove the code for the listener.

class MainActivity: AppCompatActivity() {
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		
		// add this here to bind it
		binding.myViewModel = viewModel
		
		viewModel.count.observe(this, Observer {
			binding.countText.text = it.toString()
		})
	
		// delete setOnClickListener method
	}
}

Integrate LiveData with DataBinding

This avoids us the need to explicitly declare the Observer.

class MainActivity: AppCompatActivity() {
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		binding.myViewModel = viewModel
		
		// now we will remove methods such as this one
		viewModel.count.observe(this, Observer {
			binding.countText.text = it.toString()
		})
	
	}
}

Instead we will use LiveData as a DataBinding source in the .xml layout file.

We want to display the following count variable, but it’s an Int. To display it, we need to convert that Int to String.

class MainActivityViewModel: ViewModel() {
	var count = MutableLiveData<Int>()
	...
}

We go to activity_main.xml and declare android:text tag. Then we will use LiveData directly as the DataBinding source.

<TextView
	...
	android:text="@{String.valueOf(myViewModel.count)}"
	... />

There’s one special thing left to do. LiveData is always associated with the lifecycle of an activity or a service, so we have to provide the actual lifecycle owner to the view model object. In MainActivity we need to set the current activity as the lifecycle owner of the binding object.

class MainActivity: AppCompatActivity() {
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		
		// add this
		binding.lifecycleOwner = this
		binding.myViewModel = viewModel
	}
}

encapsulate data

If we want to encapsulate data for more security, we may declare it as private and create a public variable to access it.

class MainActivityViewModel: ViewModel() {
	private var count = MutableLiveData<Int>()
	val countData: LiveData<Int>
	get() = count
	
	...
}

We must also change the .xml value.

<TextView
	...
	android:text="@{String.valueOf(myViewModel.countData)}"
	... />

Two way data binding

We use one-way DataBinding to show the user some data or get user input.

With two way data binding when the value of the object changes, the UI changes, and when the UI changes, the value of the object changes.

We have the following ViewModel.

class MainActivityViewModel: ViewModel() {
	val userName = MutableLiveData<String>()
	
	init {
		userName.value = "Frank"
	}
}

Open main_activity.xml and surround the existing contents by <layout> tags.
Write <data> tags to reference MainActivityViewModel.
Bind the value of userName MutableLiveData to the TextView.

<layout>
	<data>
		<variable
			name="viewModel"
			type="es.codes.mario.templates.MainActivityViewModel"/>
	</data>
	<android.constraintlayout.widget.ConstraintLayout>
		<TextView
			...
			android:text="@{viewModel.userName}"
			... />
	</android.constraintlayout.widget.ConstraintLayout>
</layout>

Go to MainActivity and

  1. Define a DataBinding object.
  2. Replace setContentView().
  3. Define a reference variable for the view model.
  4. Since we are using livedata with data binding, set this activity as the lifecycle owner.
class MainActivity: AppCompatActivity() {

	private lateinit var binding: ActivityMainBinding
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
		binding.viewModel = viewModel
		binding.lifecycleOwner = this
	}
	
}

Up until here this is one way data binding. The view displays LiveData values.

For two way data binding, bind the data in an EditText the same way, but use the expression @={} instead of @{}.

<layout>
	<data>
		<variable
			name="viewModel"
			type="es.codes.mario.templates.MainActivityViewModel"/>
	</data>
	<android.constraintlayout.widget.ConstraintLayout>
		<TextView
			...
			android:text="@{viewModel.userName}"
			... />
		
		<EditText
			...
			android:text="@={viewModel.userName}"
			... />
	</android.constraintlayout.widget.ConstraintLayout>
</layout>