Getting Started with Data Binding in Android Development

Getting Started with Data Binding in Android Development

Author: Mark Zhai

Original: http://blog.zhaiyifan.cn/2016/06/16/android-new-project-from-0-p7/

Introduction

Data Binding has been available since its release at last year’s Google I/O, and it has been nearly a year now. Over this year, from Beta to a more stable version, and from Android Studio 1.3 to the current 2.1.2, Data Binding has become a technology that is quite usable and can significantly improve productivity.

Getting Started with Data Binding in Android Development

However, in reality, there are still relatively few companies and projects that truly use Data Binding. This may be due to stability concerns, a lack of familiarity with the Data Binding technology itself, or simply a lack of pursuit of new technologies.

Our company has comprehensively used Data Binding technology in new products. Whether it’s me or the new engineers who are directly facing Data Binding, we all find it indispensable; once you get used to it, it’s hard to stop.

I hope that after reading this article, more friends will take an interest in using Data Binding and participate in its discussion.

Demo Source Code Repository: DataBindingSample

What is Data Binding

Data Binding, as the name suggests, is a way to bind data to XML directly, implemented by Google for MVVM in Android, allowing for automatic updates. The latest version also supports two-way binding, although the usage scenarios are not that many.

Data Binding can improve development efficiency (saving a lot of Java code that used to be written by hand), has high performance (even surpassing handwritten code), and offers strong functionality (powerful expression support).

Uses

  • Eliminate most UI code within Activities & Fragments (setOnClickListener, setText, findViewById, etc.)

  • XML becomes the only real source of UI

  • Reduce the main purpose of defining view IDs (data binding occurs directly in XML)

Open Source Solutions

  • ButterKnife, a well-known library by Jake, can reduce a lot of findViewById, setOnClickListener, and instead use annotations to generate code.

  • Android Annotations, also through annotations, with a lot of annotations, which are quite intrusive and require following its specifications to write some code, such as operations on Views can only be done under the @AfterViews annotation.

  • RoboBinding, a solution very similar to Data Binding, also does many things in XML, using AspectJ for generation. Besides these well-known ones, there are many different solutions, but since the release of Data Binding, they have all lost their place, as Data Binding is better in terms of performance, functionality, and IDE support.

Advantages

  • UI code is placed in XML, tightly coupling layout and data

  • Performance exceeds handwritten code

  • Ensures execution on the main thread

Disadvantages

  • IDE support is still not perfect (hints, expressions)

  • Error messages are not very direct

  • Refactoring support is poor (refactoring in XML will not automatically modify Java code)

Usage

It is very simple to use; you just need to add a few lines of code in the app module’s build.gradle.

Gradle

android {
    …
    dataBinding {
        enabled = true
    }
}

Layout Tag

To turn a normal layout into a data binding layout, only a few modifications are needed:

<layout>
    // Original layout
</layout>

Just wrap the layout tag around the outermost XML, and after modification, you can see that the corresponding *Binding class for that layout has been generated.

Binding Generation Rules

The default generation rule: XML is generated through the file name, using underscores to separate camel case.

For example, activity_demo.xml will generate ActivityDemoBinding, and item_search_hotel will generate ItemSearchHotelBinding.

The view generation rule is similar, but since it is a class variable, the first letter is not capitalized; for example, if a TextView has an ID of first_name, it will generate a TextView named firstName.

We can also customize the generated class name, just need to:

<data class="ContactItem">…</data>

Then the generated class will be named ContactItem.

Basic Usage

Generating Binding Instances

All Binding instances can be generated through DataBindingUtil, with the method name consistent with the original inflate method of that view, such as activity still being setContentView, just adding a parameter to obtain the activity.

Removing findViewById

After using Data Binding, we no longer need findViewById because all views with IDs have already been initialized in the Binding class and can be accessed directly through the binding instance.

Variable Binding

Using the data tag, we can declare variables in XML and use the field of that variable, setting it through the binding instance. For example:

<data>
    <variable
        name="employee"
        type="com.github.markzhai.databindingsample.Employee"/>
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".DemoActivity">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{employee.lastName}"
        android:layout_marginLeft="5dp"/>
</LinearLayout>

Then we can use it in Java code:

binding.setEmployee(employee);
// Or directly through setVariable
binding.setVariable(BR.employee, employee);

Event Binding

Strictly speaking, event binding is also a type of variable binding. We can directly bind in XML:

  • android:onClick

  • android:onLongClick

  • android:onTextChanged

Method Reference

Usually, a class named Handler or Presenter is defined in Java code, and then set in, with the method signature needing to match the corresponding listener method.

<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:bind="http://schemas.android.com/apk/res-auto">
    <data>
        <import type="android.view.View"/>
        <variable
            name="employee"
            type="com.github.markzhai.databindingsample.Employee"/>
        <variable
            name="presenter"
            type="com.github.markzhai.databindingsample.DemoActivity.Presenter"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        tools:context=".DemoActivity">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Enter First Name"
            android:onTextChanged="@{presenter::onTextChanged}"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{presenter.onClick}"
            android:text="@{employee.firstName}"/>
    </LinearLayout>
</layout>

In Java code:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    binding.setPresenter(new Presenter());
    ...
}

public class Presenter {
    
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        employee.setFirstName(s.toString());
        employee.setFired(!employee.isFired.get());
    }

    public void onClick(View view) {
        Toast.makeText(DemoActivity.this, "Clicked", Toast.LENGTH_SHORT).show();
    }
}

Listener Binding (Lambda)

It is possible not to follow the default method signature:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:visibility="@{employee.isFired ? View.GONE : View.VISIBLE}"
    android:onClick="@{() -> presenter.onClickListenerBinding(employee)}"/>
public class Presenter {

    public void onClickListenerBinding(Employee employee) {
        Toast.makeText(DemoActivity.this, employee.getLastName(), Toast.LENGTH_SHORT).show();
    }
}

Data Binding Principles

Narrow Principle

Narrowly speaking, we can directly observe its working principle by calling interfaces and generated classes.

As an entry point, let’s take a look at the interface of DataBindingUtil:

public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
        DataBindingComponent bindingComponent) {
    activity.setContentView(layoutId);
    View decorView = activity.getWindow().getDecorView();
    ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
    return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}

private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
        ViewGroup parent, int startChildren, int layoutId) {

    final int endChildren = parent.getChildCount();
    final int childrenAdded = endChildren - startChildren;

    if (childrenAdded == 1) {
        final View childView = parent.getChildAt(endChildren - 1);
        return bind(component, childView, layoutId);
    } else {
        final View[] children = new View[childrenAdded];
        for (int i = 0; i < childrenAdded; i++) {
            children[i] = parent.getChildAt(i + startChildren);
        }
        return bind(component, children, layoutId);
    }
}

We can see that it will then run to the specific Binding class:

public ItemFeedRecommendUserBinding(android.databinding.DataBindingComponent bindingComponent, View root) {

    super(bindingComponent, root, 9);

    final Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);

    this.mboundView0 = (android.widget.LinearLayout) bindings[0];

    this.mboundView0.setTag(null);

    this.recommendUserFirst = (com.amokie.stay.databinding.IncludeRecommendUserBinding) bindings[1];

    this.recommendUserFourth = (com.amokie.stay.databinding.IncludeRecommendUserBinding) bindings[4];

    this.recommendUserSecond = (com.amokie.stay.databinding.IncludeRecommendUserBinding) bindings[2];
    
    this.recommendUserThird = (com.amokie.stay.databinding.IncludeRecommendUserBinding) bindings[3];

    setRootTag(root); // listeners
    invalidateAll();
}

We can see that all views are initialized all at once, which is evidently faster than doing findViewById one by one.

In addition to view initialization, in executeBindings, it will determine whether each field needs to be updated through mDirtyFlags, and its setting is done through each set function.

Process Principle

Getting Started with Data Binding in Android Development

  • Process layout files -> Convert to layout files without data binding

  • Parse expressions -> Ensure expression syntax is correct

  • Parse dependencies -> user.isAdmin, isAdmin is a field or method…

  • Setter -> such as visibility

  • Performance

  • 0 reflection

  • findViewById requires traversing the entire view group, while now it only needs to initialize all required views once

  • Uses bit flags to check for updates (dirtyFlags)

  • Data changes will trigger operations only on the next batch update

  • Expression caching, no repeated calculations during the same refresh

  • Advanced Usage

    Expressions

  • Arithmetic + – / * %

  • String concatenation +

  • Logic && ||

  • Bitwise & | ^

  • Unary + – ! ~

  • Shift >> >>> <<

  • Comparison == > < >= <=

  • Instanceof

  • Grouping ()

  • Literal – character, String, numeric, null

  • Cast

  • Method calls

  • Field access

  • Array access []

  • Ternary ?:

It does not yet support this, super, new, and explicit generic calls. It is worth mentioning that there is also a null-coalescing operator, such as:

android:text="@{user.displayName ?? user.lastName}"

Which takes the first non-null value as the result.

Here is a common example where the margin of a certain view is the margin of its left ImageView plus the width of that ImageView. In the past, we may need to define a dimension to hold these two values, but now we just need:

android:marginLeft="@{@dimen/margin + @dimen/avatar_size}"

We can even directly concatenate strings, such as:

android:text="@{@string/nameFormat(firstName, lastName)}"

<string name="nameFormat">%s, %s</string>

Avoiding Null Pointers

Data binding will automatically help us avoid null pointers; for example, @{employee.firstName}, if employee is null, then employee.firstName will be assigned a default value (null). For ints, it will be 0.

It is important to note the array out of bounds, as this is XML and not Java, so there is no place to check the size. Include

<include layout="@layout/name" bind:user="@{user}"/>

For included layouts, the usage is similar, but it is necessary to bind twice inside: the outer layout that includes the layout uses bind:user to set it in.

It should be noted that the top-level of the included layout must be a ViewGroup; currently, the implementation of Data Binding, if the top level is a View instead of a ViewGroup, the binding index will conflict (be overwritten), resulting in some unexpected results.

ViewStubs

ViewStub is special; it is not visible before being actually inflated, so a special scheme is used, with a final ViewStubProxy to represent it and listen to ViewStub.OnInflateListener:

private OnInflateListener mProxyListener = new OnInflateListener() {
    
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        mRoot = inflated;
        mViewDataBinding = DataBindingUtil.bind(mContainingBinding.mBindingComponent, inflated, stub.getLayoutResource());
        mViewStub = null;

        if (mOnInflateListener != null) {
            mOnInflateListener.onInflate(stub, inflated);
            mOnInflateListener = null;
        }

        mContainingBinding.invalidateAll();

        mContainingBinding.forceExecuteBindings();
    }
};

It will only be initialized during onInflate.

Observable

A pure Java ViewModel class will not update the UI when it is updated. With data binding, we certainly hope that when data changes, the UI will refresh immediately; Observable is the concept born for this purpose.

BaseObservable

The class inherits from BaseObservable:

private static class User extends BaseObservable {

    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }
    
    @Bindable
    public String getLastName() {
        return this.lastName;
    }
     
    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

BaseObservable provides a series of notify functions (actually notifyChange and notifyPropertyChanged); the former refreshes all values, while the latter only updates the corresponding BR flag, and the BR is generated through the @Bindable annotation. In the above example code, we can see that both get methods are annotated, so we can access them through BR and notify specific property changes.

Observable Fields

If every bindable property needs to create an Observable class, that would be too cumbersome. Therefore, Data Binding also provides a series of Observable types, including ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable. We can also declare other types through ObservableField generics, such as:

private static class User {

    public final ObservableField<String> firstName = new ObservableField<>();

    public final ObservableField<String> lastName = new ObservableField<>();

    public final ObservableInt age = new ObservableInt();
}

In XML, the usage is the same as normal String and int, but it will refresh automatically; however, accessing it in Java can be relatively cumbersome:

user.firstName.set("Google");int age = user.age.get();

Relatively speaking, having to get/set each time is quite troublesome; I personally think it is better to inherit from BaseObservable directly.

Observable Collections

Some applications use more dynamic structures to store data; in this case, we would like to use a Map to store the data structure. Observable provides ObservableArrayMap:

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

In XML, we can access them directly by index key:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
   
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

When we do not want to define a key, we can use ObservableArrayList:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

In the layout, we can directly access them by numeric index.

Dynamic Variables

Sometimes, we do not know what the generated binding class is. For example, in RecyclerView, there may be multiple ViewHolders, and the holder we get is just a base class (how this base class is written will be discussed in the next article), at this time, we can define the same variable name in these item layouts, such as item, and directly call setVariable:

public void onBindViewHolder(BindingHolder holder, int position) {

    final T item = mItems.get(position);
    holder.getBinding().setVariable(BR.item, item);
    holder.getBinding().executePendingBindings();
}

executePendingBindings will force an immediate refresh of the binding changes.

References

https://developer.android.com/topic/libraries/data-binding/index.html

About Java and Android Experts Channel

Java and Android Experts Channel is a public account with tens of thousands of followers discussing Java and Android development, sharing and creating the most valuable dry goods articles, helping you become an expert in this field!

We discuss the cutting-edge technologies of Android and Java development: Android performance optimization, plug-in development, cross-platform, dynamic development, strengthening and anti-cracking, etc., and also discuss design patterns/software architecture. It is composed of a team of engineers from BAT.

Follow us to receive red envelopes, reply: “Baidu”, “Ali”, “Tencent” for surprises!!! After following, you can join the WeChat group. The group consists of experts from Baidu, Ali, and Tencent.

Welcome to follow us, discuss technology together, scan and long press the QR code below to quickly follow us. or search for the public account: JANiubility.

Getting Started with Data Binding in Android Development

Public Account:JANiubility

Getting Started with Data Binding in Android Development

Leave a Comment

×