Benmccanny databindinginpractice cover

Data Binding in Practice

If you enjoy talks from 360 AnDev, please support the conference via Patreon!

The Data Binding library presents a new way to construct Android UIs, with massive time savings for high interaction apps that make use of two-way binding.

We will start by looking at these features and ease into more advanced concepts like one-way binding, binding adapters, observables, one-way binding, and events. Walking through the construction of a detailed sample app, we will see the real world implications of a fully two-way, MVVM approach and its effects on views, project structure, and UI component communication. We will discuss pitfalls, best practices and the kinds of projects that can benefit most from this approach.


Introduction

My name is Ben McCanny, and I’m a UX engineer on the Android and Chrome OS design teams at Google.

Data Binding Motivations

My own Android prototypes often had no architecture, and when I heard about data binding, I wondered if it offered a better development paradigm.

To learn the ropes, I built a drawing app, which you can check out at github.com/google/spline. I discovered not only were there advantages to building from the ground up with data binding, but the library provides a number of powerful tools you can make use of today, without rearchitecting the app.

Getting started

To get started, let’s imagine what our app’s editor activity might look like without binding: boilerplate lines for view initialization, and code to reference the view by ID. With data binding, you will never have to call the method findViewById.

To start, signal to the ID you want to generate a binding class for the layout. We do this by wrapping our layout file in our layout tag.

Next, replace that content view with the data binding utils equivalence. The generated binding classes name is based on the name of the layout file; underscores converted to camel case.

With the reference to the generated binding, we can access the view objects, out of the box, as properties of the generated class.

One-way binding

Avoiding boilerplate

At the top of our layout file, declare a variable with name document, and in this case, the data type of our custom data object.


<data>
  <variable name="Document"
            type="com.googleplex.proto.spline.model.Document"/>
</data>

Then we can replace our property setting lines. With this call to binder layout to the document object:


mBinding.setDocumetn(mDocument);

The name of this setter is automatically generated in activity editor binding, based on the variable name, which is set in the layout file.

Get more development news like this

Now we can set properties with data binding expressions:

...
android:title="@(document.filename)"/>

We set the toolbar title with the file name string from our data object. @{} denotes a data binding expression. Anything within the braces is evaluated as “an almost Java” expression. Expressions work with any attribute, except style and some layout attributes, unfortunately.

Expression examples


// Field access
android:text="@{layer.name}"

// Field of fields
android:text="@={document.currentLayer.name}"

// Ternary operator expressions and float literals
android:text="@{layer.visible ? 1.0f : 0.33f}"

// Can access resources!
android:background="@{layer.selected ? @color/colorAccent : @color/sidePanel}"

Data Binding Makes Custom Attributes Awesome

Data binding makes custom attributes much easier. Whether you want to add interactivity, by binding to dynamic data or if you want to configure your custom view with static values in your layout file.

Before data binding, if you had a custom view, and you wanted to set a custom attribute in the XML layout, such as hue, you had to first declare the attribute in attributes.xml, then implement logic in your custom view code to extract the attribute values past to the view constructor as an attribute set.


<resources>
  <declare-styleable name=SaturationValuePicker">
    <attr name="hue" format="float"/>
  </declare-styleable>

</resources>

With data binding enabled, we can simply set the custom attribute in the XML, making sure that we use the @ sign brace notation to make it a data binding expression. And then, if the custom view has a setter taking the type of data passed in the layout, it will get passed to the setter automatically

<com.example.view.SaturationValuePicker
  ...
  app:hue="@{189}"/>
public void setHue(float hue) {

}

If the data type from our data object does not match the type that a framework attribute expects, binding adaptors can help.

Binding adapters

In the drawing app, there’s a text field for a date. But the date is represented as an integer timestamp. Binding adapters denoted with binding adapter annotations give us a place to take the input data from the binding expression and modify the view as we need.


<TextView
  ...
  android:text="@{viewModel.selectedFile.lastModified}"

In the case of the drawing app example, here’s the binding adaptor:


@BindingAdapter("android:text")
public static void setText(TextView view, long date) {
  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  String formatted = format.format(date);
  view.setText(formatted);
}

We take the input long date, format it with a simple date format, and then set the views text programatically. This will apply to all attribute expressions that match the attribute string, the view past as the first parameter, and the data type of the second parameter.

You can rewrite SDK view attributes to suit your needs. The binding adaptor annotations form the glue between XML and Java, while also allowing you to bury the adaptors anywhere in your project. Even in a library, data binding will locate them.

Two-way binding

For a lucky set of attributes and views, two-way binding is already built it in, enabling it requires a small change. Adding an equal sign after the @ sign:

android:progress="@={currentItem.opacity}"

Here’s a list of views and attributes with built-in to a binding.

  • AbsListView - android:selectedItemPosition
  • CalendarView - android:date
  • CompoundButton, Checkbox, ToggleButton - android: checked
  • DatePicker - android:year, android:month, android:day
  • NumberPicker - android:value
  • RadioGroup - android:checkedButton

Custom Two Way Binding

To add support for two way binding of the view saturation, we need to declare an inverse binding method. This tells data binding which method is the getter, for the saturation attribute. Inverse binding methods is a class-level annotation that can be added to any class in your project.


@InverseBindingMethods({
  @InverseBindingMethod(
    type = SaturationValuePicker.class,
          attribute = "saturation"
          method = "getSaturation")
})

public class SaturationValuePickerBindingAdapters {

}

Next, we need to create a setter for the saturation attribute changed. This is a synthetic attribute that data binding passes to our view without us setting it in XML.


public void setSaturationAttrChanged(InverseBindingListener listener) {
  mSaturationAttrChangedListener = listener;
}

We need this reference to complete the two-way binding. Invoking the inverse binding listener, the binding listener is on change callback, and our setter allows us to notify data binding of the change in the bound property.


public void setSaturation(float saturation) {
  if (saturation != mSaturation) {
    mSaturation = saturation;
    mColor.setSaturation(saturation);
    updateSelectedColor();
    updateSelectorX();
    if (mSaturationAttrChangedListener != null) {
      mSaturationAttrChangedListener.onChange();
    }
  }

}

Finally, we can use the add sign equal notation with our attribute:

...
app:saturation="@={color.saturation}"/>

ColorPicker: communicating with two-way bindings

Let’s explore the application flow that happens when the color value is changed, and how the components use two-way binding to communicate. A re-bind to the color picker’s color attribute in the layout file triggers a call to the set color method.

Because color picker is comprised of a number of different views, a number of different independent components, such as the hue picker, the saturation value picker, and edit text fields - it has its own layout and binding. Set color updates the bindings color, which triggers a rebind of hue, saturation and value attribute in the color picker layout.

This rebind invokes set hue on hue picker, which in turn sets the color and the translation of the selector handle. On the saturation value picker, it similarly triggers many updates.

DocumentView: Binding to Data Structures

Binding to a data structure like a list or a tree can be a little trickier than binding to a data object or field.

In the drawing app, the data structure we have is the documents layer tree - series of layers descending from a root layer group. The layer tree binds to an instance of the monolithic document view class which renders the layer as a canvas of color shapes on screen.

To bind to all the layers of the documents layer tree, the best we can do is bind to the root node. Thus we’ve only bound to one node, we need to wait to track the changes in the rest of the tree.

Enter observable lists on list change callback, and on property change callback. With the on list change call back, whenever a layer is moved, removed or changed from an observable list of layers, we redraw the document with a call to invalidate.

mOnPropertyChangedCallback = new Observable.OnPropertyChangedCallback() {

  @Override
  public void onPropertyChanged(Observable observable, int i) {
    invalidate();
  }
};

When a layer is added, we invalidate and add on property change call back as well as an on list change call back.

In summary, binding data structures is possible but is less convenient than binding to primitives. Furthermore, property changes listeners are annoying - they don’t always get set when you want.

RulerView

Ruler view demonstrates one of the hiccups you might encounter with multiple two way bound views, animating in concert.

The ruler views rely on the two-way bindings with the document views viewport and current layer properties.

...
app:viewportStart="@={viewModel.viewportX}"

They are two way bound so that you can drag and fling the viewport directly on these views. With a simple vibe version of the touch logic that the document view has, for a single axis. The gray highlight of the ruler view comes from the currently selected layer and start point, and its size is determined by the current layer’s dimensions via the binding expression.

Building the ruler view raises the issue of having to be careful with separate views and animating the same bound property. In the demonstration, you can see the ruler view fling is one frame out of sync with the document view.

To address this, we can call our binding classes, execute pending bindings - a method that tells data binding to execute all outstanding changes to bound properties immediately.

LayerListView: RecyclerView and binding

LayerListView is a RecyclerView subclass, primarily needed as a way of binding, indirectly binding properties to the adaptor. Here you can see the layerless view passing down properties current layer to the adaptor.

public void setCurrentLayer(Layer currentLayer) {
  mAdapter.setCurrentLayer(currentLayer);
}

public void setRoot(LayerGroup root) {
  mAdapter.setRoot(root);
}

The binding class has reference to ID tag views and binding of data to attributes, meaning it’s the only thing we need to hold a reference to in the view holder, and a callbacks class.


public static class LayerRowHolder extends RecyclerView.ViewHolder {
  private LayoutLayerRowBinding binding;
  ...
}

The callbacks class I’m using to maintain a reference to the adaptor for invoking adaptor methods, so the view holder or the binding doesn’t have to. View binding then becomes simple.

Data binding design patterns: MVVM

In our drawing app, if we structured our view to only receive an update data through the binding, then there is a nice separation of concerns and we can abstract our architecture to these two components. The view and the data model bound together with data binding is a great pattern to follow for simple cases.

But as a manipulation of model data for our presentation in the UI increases, either within the binding expressions or within the model itself, the need to introduce a new layer increases.

We insert a view model between the view and model layers. Where the model is the purest expression of our apps domain and persistent state, the view model represents the data as it should be presented in the view layer.

In this pattern, which we call MVVM, the view model is bound to the view and then reads and then updates data in the model layer. MVVM applied to our drawing app creates a normalization of the data model on custom views. You can see that normalization visually in your ID. MVVM implies a logical package organization if you choose to do so.

layout_layer_row revisited

Let’s see what impact the view model has on this file. The view model allows us to take all these turner expressions, and turn them into fields of the view model, where the view models scatter and centers are tailored towards presentation values.

There are a couple of challenges with this approach: Data binding treats an integer binding expression on a color property, as a color, rather than as a resource ID, unless it’s an ID literal in the expression. As a result, moving from expressions with literals to a property that returns an int causes us frustration when the colors and drawables suddenly don’t work anymore as expected.

To fix this, we can employ binding adaptors to transform the IDs into the drawables or colors we need, and set them on the views in question.

Here are some examples of what those binding adaptors might look like. Take the input ID, grab the related resource, and apply it to the view. The second challenge has to do with introducing the view model layer into our app. In other view models on the app, e.g. the document view model, the view model is made observable and used everywhere as a reference to the model object as needed.

Challenges I Encountered

  • Sometimes two-way binding can be a real pain. Sometimes you’re replacing code you wrote that you don’t understand with generating code that you also don’t understand.

  • With binding’s emphasis on compiled time generation and checks, you spend more time fixing compilers than debugging runtime exceptions.

  • Sometimes there’s the clever way to make it work with binding, and it might take extra thought.

  • The universality of binding adaptors for one attribute, data type, view type set can be both a blessing and a curse. Sometimes they can be too specific; oftentimes, they’re too general and apply themselves to views that you don’t want them to.

  • Binding expressions not supporting the style attribute is a big bummer. Some things are difficult or impossible to set in XML without style.

Who should use data binding?

Data binding offers something that nearly every project can benefit from. Everyone can save time and effort by taking advantage of the four no rearchitecting required features.

You can think about data binding as a library for a different way of architecting your apps. But it’s also a set of compiled time tools built into Android Studio that make your Android development workflow easier. Because this happens at compile time, there should be any impact on performance, and it might even make your apps speedier.

What projects can benefit most from fully investing into a binding and MVVM?

Two-way binding offers easy modularity of your components and a strong separation of concerns. The view components can easily communicate through updates to the view model.

What projects can most leverage these benefits? You can characterize Android applications along with a continuum:

  • Flow-oriented applications: where the interaction is primarily moving from activity to activity or fragment to fragment. The Android settings app is a good example of this.

  • Interaction-oriented applications, this is the Android equivalent of single page web apps.

As you move toward interaction oriented applications, you will get an increasing value of data binding.

Resources

The sample app source code is available at https://github.com/google/spline. George Mount, one of the authors of data binding has some great Medium posts that deep dive into a number of different topics. And lastly, there is always the developer documentation.

Next Up: Android Architecture Components and Realm

General link arrow white

About the content

This talk was delivered live in July 2017 at 360 AnDev. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.

Ben McCanny

Ben is a UX Engineer who works on the Android and Chrome OS System UI design teams. He is passionate about prototyping, motion design, and building clean, innovative UI.

4 design patterns for a RESTless mobile integration »

close