Multiple Choice implementation using DataBinding in Android

Suchandrim Sarkar
AndroidPub
Published in
3 min readApr 5, 2019

We often have to implement a feature where the user has to choose among a list of choices and based on the selection we have to change the UI of the other options, something like a radio group. When I started building Jade, which is a mood tracker app, I realized I have to make a UI where the user selects a mood from the list of options as shown below. When a user makes a choice, the UI of the selection is changed (The Image and Text color) and the other options’ UI is reset

The most common and straightforward approach for implementing this is something like this in pseudocode:

fun userSelected(choice: Int) {
when 0 -> {
changeUI(0)
resetUI(1)
resetUI(2)...
}
when 1 -> {...}
...
}

Though the above can be improved, I wanted to use Databinding and remove as much boilerplate code as possible, and probably not having to depend on the activity at all, just the XML and ViewModel.

Note: In the code below, the ViewModel extends BaseObservable instead of ArchitectureComponent ViewModel to make the code simpler. If you do use the latter, the ViewModel variable needs to be wrapped with LiveData before being used in XML.

First, you need to enable native data binding in your app level build.gradle

dataBinding {
enabled = true
}

Then in your ViewModel, extend BaseObservable and create a boolean array which keeps track of your selection. Create a method to update the array based on user’s selection. Then notifyChange() notifies listeners of the property change and thus updates the UI:

var moodSelection = BooleanArray(6)

fun moodSelected(mood: Int) {
moodSelection.fill(false)
moodSelection[mood] = true
notifyChange()
}

Finally in your XML:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="viewModel"
type="com.example.yourapp.YourViewModel"
/>
</data>
...
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.moodSelected(0)}"
android:orientation="vertical"
>
<ImageView
android:id="@+id/mood_happy"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@{viewModel.moodSelection[0] ? @drawable/selected_img : @drawable/default_img}"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/happy"
android:textColor="@{viewModel.moodSelection[0] ? @color/selectedTextColor : @color/defaultTextColor}"
/>
</LinearLayout>
...
</layout>

OnClick from the XML updates the data of the boolean array in ViewModel based on your choice passed as parameter and updates the UI.

So this was very Simple.

Let’s notch it up a bit. I added a dark mode feature in Jade, which meant there are two default colors (defaultTextColor and defaultTextColorDark), and each mood has two default images, based on dark/light mode. So the following code won’t work anymore:

android:textColor="@{viewModel.moodSelection[0] ? @color/selectedTextColor : @color/defaultTextColor}"

This is where BindingAdapter comes into the picture. BindingAdapter lets you create custom attributes (or setter) and bind them to actions. Example:

@BindingAdapter("android:bufferType")
public static void setBufferType(TextView view, TextView.BufferType bufferType) {
view.setText(view.getText(), bufferType);
}

In the above example, when android:bufferType is used on a TextView, the method setBufferType is called.

We can use BindingAdapter to send the information (Dark/Light mode, defaultTextColor, defaultTextColorDark etc) via parameters and let the setter function set the properties to the view. But it’s not straightforward for BindingAdapters to accept multiple parameters. Here’s how t’s done

@BindingAdapter(value = ["selectedSrc", "defaultSrc", "darkSrc", "selected", "isDarkMode"], requireAll = false)
@JvmStatic
fun setImageViewSrc(
imageView: ImageView,
selectedSrc: Drawable,
defaultSrc: Drawable,
darkSrc: Drawable,
selected: Boolean,
isDarkMode: Boolean
) {
if (selected) {
imageView.setImageDrawable(selectedSrc)
} else {
if (isDarkMode) {
imageView.setImageDrawable(darkSrc)
} else {
imageView.setImageDrawable(defaultSrc)
}
}
}

@BindingAdapter(value = ["selectedTextColor", "defaultTextColor", "darkTextColor", "selected", "isDarkMode"], requireAll = false)
@JvmStatic
fun setTextColor(
textView: TextView,
selectedTextColor: Int,
defaultTextColor: Int,
darkTextColor: Int,
selected: Boolean,
isDarkMode: Boolean
) {
if (selected) {
textView.setTextColor(selectedTextColor)
} else {
if (isDarkMode) {
textView.setTextColor(darkTextColor)
} else {
textView.setTextColor(defaultTextColor)
}
}
}

And in the XML:

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.moodSelected(0)}"
android:orientation="vertical"
>
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
bind:selectedSrc="@{@drawable/selected_img}"
bind:defaultSrc="@{@drawable/default_img}"
bind:darkSrc="@{@drawable/default_dark_img}"
bind:isDarkMode="@{viewModel.darkMode}"

bind:selected="@{viewModel.moodSelection[0]}"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text"
bind:selectedTextColor="@{@color/selectedTextColor}"
bind:defaultTextColor="@{@color/defaultTextColor}"
bind:darkTextColor="@{@color/defaultTextColorDark}"
bind:isDarkMode="@{viewModel.darkMode}"

bind:selected="@{viewModel.moodSelection[0]}"
android:textSize="12sp"
/>
</LinearLayout>

This resulted in no boilerplate code in the Activity and minimal code in the ViewModel. To enable multiple selections, just replace the ViewModel moodSelected method with this

fun moodSelected(mood: Int) {
moodSelection[mood] = !moodSelection[mood]
notifyChange()
}

--

--