Sunday, August 28, 2022

Filling the gaps of Android Databinding

tl;dr

LibraryDescription
binding-collection-adapterBind RecyclerView, ListView, ViewPager and Spinner
bindable-radiobuttonsBind RadioButton, ToggleButton, Checkbox and lists of Buttons
useful BindingAdapterReduce boilerplate further and the need for common support properties

Introduction

The Android Databinding library is tried and tested tool for enabling MVVM architecture in your Android apps. It helps you move code from your activities, fragments and views to your ViewModel. From the outset the library lacked what I belive to be some core features, like binding to RecyclerView and groups of RadioButtons. In this article I will present a couple of libraries that fill these gaps and more. 

With these simple add-ons I believe DataBinding is a highly effective and fun way of coding solid unit-testable MVVM apps on Android.

List- And RecyclerViews

Evan Tatarka's Binding-Collection-Adapter library solves binding to RecyclerViews elegantly. You have one XML view for the individual items and a ViewModel class that represents them. If you have multiple view types for the list you can override the binding code.

This solution rids of the adapter. Less code. Better architecture. It also suppoerts ListView, Spinner and ViewPager

Example

Below is a minimal example to lay out the concept. A RecyclerView with two bound, each item has the text and the color property bindable. Read the official documentation for a complete, compilable example.

Result:

Main view:

<androidx.recyclerview.widget.RecyclerView
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
	app:items="@{viewModel.items}"
	app:itemBinding="@{viewModel.itemBinding}"/>

Item view:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="itemVM" type="com.nilsenlabs.bindingblog.ItemVM" />
    </data>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@{itemVM.text}"
        android:textColor="@{itemVM.color}">
    </TextView>
</layout>

Main ViewModel


class MainVM {
    val items: ObservableList = ObservableArrayList()
    val itemBinding: ItemBinding = ItemBinding.of(BR.itemVM, R.layout.item)

    init {
        items.add(ItemVM("One", Color.BLACK))
        items.add(ItemVM("Two", Color.GREEN))
    }
}

Item ViewModel


class ItemVM(
    val text: String,
    val color: Int
)

RadioButtons, CheckBoxes and list of Buttons

This one I built myself: I needed to bind a list of RadioButtons to data fetched from a remote service. The number of RadioButtons will vary. A common use case (with or without the service), but not supported out of the box from Google's Data Binding library. My library, bindable-radiobuttons, also support check boxes, toggle buttons and regular buttons. To produce the screenshot below implement the following:

View:


<com.nilsenlabs.bindableradiobuttons.radiobuttons.BindableRadioButtonList
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:orientation="vertical"
	app:itemViewId="@layout/my_radio"
	app:selectedItem="@={viewModel.selectedRadioButton}"
	app:buttons="@{viewModel.radioButtons}"
	/>

ViewModel


val radioButtons = listOf(
	RadioButtonViewModel("One"),
	RadioButtonViewModel("Two", isEnabled = false),
	RadioButtonViewModel("Three")    
)
val selectedRadioButton = ObservableField<RadioButtonViewModelInterface>(radioButtons[2])

Useful Binding Adapters

You need a handful of extra binding adapters on top of what Google provides to solve a set of common use cases.

Gist with BindingAdapters on Github here

NameInput typeBinds toFunction
app:visibleOrGoneBooleanVisiblitytrue means Visible, false means Gone. My number 1 most used binding adapter.
app:visibleOrInvisibleBooleanVisiblitytrue means Visible, false means Invisible
app:goneIfNullAnyVisiblitynull means Gone, non-null means Visible. Perfect if you want a text view to be gone when the String is null.
app:onClickNoneclickListenerJust a click listener that doesn't send "view: View" as unnecessary input parameter
android:srcIntsrcAllows you to bind a drawable resources as Int to an ImageView. Google only allows Drawable objects out of the box, which will force you to depend on a Resource object in the VM
android:textInttextAn improvement to standard TextView text binding that doesn't crash when the value 0 is bound.