Using Android DataBinding in CustomViews

Halil Ozercan
4 min readNov 29, 2018

DataBinding — Less Boilerplate

We probably all remember the old(like 4 years ago?) days of Android development when the presentation layer was merged into Activity code. We had to include code snippets like below everytime a view manipulation was needed.

TextView mNameTextView = (TextView) findViewById(R.id.name_textview)

These type of lines that are enforced by the framework are called boilerplate and often their number is inversely correlated with readability of the code. Good news is that this problem has been addressed for a long time. ButterKnife might be considered the most successful 3rd party implementation of View Binding concept using annotations. However, it was mostly just view binding, managing the views and their data still resided in the presentation layer.

Now, thanks to new Android Architecture Components, we have a better way for managing our View-Model relation more reactively. One crucial part of this architecture is DataBinding Library which enables developers to write simple links between ViewModel(optional) and Android Views only using XML attributes. You can check this guide to better understand how DataBinding works.

Custom Views — Write Once, Use everywhere

I believe it shouldn’t even be necessary to mention how useful and practical the “Custom Views” are. They comfortably pack their logic, have their own capsulated lifecycle management, offer in-depth customization and so much more. They especially come in handy while working on a rather large project.

Although using a common style among all instances of a view provides developers with easier style changes in the future, logical changes like new features cannot be handed by styling. However, extending a base view from Android Framework and having your custom view used in the project brings the possibility of managing common logic from the common base.

class TodoListView : FrameLayout {
...
fun addItem(item: TodoItem) { ... }
fun removeItem(index: Int) { ... }
}

Nonetheless, it can be cumbersome to write a custom view from scratch especially when it needs to support XML customizations.

<com.halilibo.todoapp.TodoListView
android:id="@+id/todo_list_view"
android:height="wrap_content"
android:width="wrap_content"
app:removable="true"
app:addIcon="@drawable/ic_add"
app:itemStyle="@style/todo_item_style"
...
/>

To be able to support XML customization like above, we need to add styleable attributes, parse them while initializing our view, default values when user input is absent and etc. We can get rid of all these by using DataBinding.

Flowing DataBinding in CustomViews

Wait, what the hell is Flowing Databinding? Well you might not be familiar with the term because I just made it up. The term comes from my frustration when I tried to google “using DataBinding in custom view for both taking data from outside and applying it inside”. Flowing DataBinding refers to the situation where a view is capable of taking data from other sources using DataBinding and also utilizes DataBinding inside to update itself. By using this approach, we can come close to almost non-boilerplate levels.

Let’s assume we are developing a Todo list application and for some reason it needs to show and manage Todo items in multiple screens inside the application. The ideal way to pass items and required configuration to the view should be like the following, “employing DataBinding”

<com.halilibo.todoapp.TodoListView
android:id="@+id/todo_list_view"
android:height="wrap_content"
android:width="wrap_content"
app:items="{viewModel.items}"
app:selectedItems={viewModel.selectedItems}
app:refreshing={viewModel.refreshing}
app:addIcon="@drawable/ic_add"
app:itemStyle="@style/todo_item_style"
...
/>

Let’s check how our TodoListView is able to read this data and pass onto its Binding

  • Create binding
private val binding: LayoutTodoListViewBinding = 
LayoutTodoListViewBinding.inflate(LayoutInflater.from(context), this, true)

Above line of code can be placed directly in TodoListView.kt. It simply creates the binding and inflates the layout.

  • Prepare DataBinding ready layout
<?xml version="1.0" encoding="utf-8"?>
<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="model"
type="com.halilibo.todoapp.TodoListView.ObservableModel" />

</data>

<merge
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{model.title}"/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
isVisible="@{model.refreshing}" />
<RecyclerView .../>
</merge>

</layout>

While going through this tutorial, you will see some helper functions and utilities. These exist to make our lives easier e.g. conversion of boolean to one of Android Visibility states is not something you would want to include in View logic. Instead, we can use BindingAdapters.

@BindingAdapter("isVisible")
fun updateVisibility(view: View, isVisible: Boolean) {
view.visibility = if(isVisible) View.VISIBLE else View.GONE
}

Also, we will be using Observables to update our bindings at the right time, meaning when we actually change their content. To do this, we can separate model part of our custom view into a BaseObservable

class NavigationViewModel : BaseObservable() {

@get:Bindable
var refreshing by ObservableDelegate(BR.refreshing, false)

@get:Bindable
var title by ObservableDelegate(BR.title, "")
}class ObservableDelegate<T>(val id: Int, initialValue: T, private val onChanged: (T) -> Unit = {}) {

var value = initialValue

operator fun getValue(self: BaseObservable, prop: KProperty<*>): T = value

operator fun setValue(self: BaseObservable, prop: KProperty<*>, value: T) {
this.value = value
onChanged.invoke(value)
self.notifyPropertyChanged(id)
}

}

ObservableDelegate class is again a utility class which provides us with a default value for our observable and trigger “notifyPropertyChanged” when the object is actually changed.

Now, only remaining part is adding the bridge between outside data and inside model.

fun setRefreshing(refreshing: Boolean) {
this.model.refreshing = refreshing
}
fun setTitle(title: String) {
this.model.title = title
}

One of the great advantages of DataBinding is that the XML field in which you put your data, automatically calls the corresponding setter function of the view e.g. app:title calls setTitle. This way, you don’t have to write styleables and additional XML values.

Finally, we can look at how data flows through our code to the final view.

First we make a request to the server and set refreshing in ViewModel of our Activity.

viewModel.refreshing = true

This triggers DataBinding in Activity’s XML.

<com.halilibo.todoapp.TodoListView 
...
app:refreshing = "@{viewModel.refreshing}"
...
/>

Corresponding setter function is called

fun setRefreshing(refreshing: Boolean) {
this.model.refreshing = refreshing
}

Now, ObservableDelegate calls notifyPropertyChanged for “refreshing” because the set function is invoked. notifyPropertyChanged correctly updates our TodoListView’s already binded XML.

<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
isVisible="@{model.refreshing}" />

isVisible calls the corresponding BindingAdapter and visibility changes.

Conclusion

To sum up, DataBinding solves boilerplate code problem while CustomViews solve code modularity problem. Combining their powers enable us to seamlessly transfer data between our Model and View. More importantly, our application becomes more reactive which is necessary for mobile applications which regularly fetch data from remote servers.

--

--