banner
jzman

jzman

Coding、思考、自觉。
github

Android Jetpack Component: DataBinding

The previous summary covered the use of ViewModel, LiveData, and Lifecycle architecture components. You can read the articles below for more details:

This article mainly focuses on the basic usage of dataBinding, with the following main content:

  1. dataBinding support
  2. Layout file configuration
  3. Binding data
  4. Special expressions
  5. Event binding
  6. Custom binding classes
  7. Others

dataBinding support#

Using dataBinding requires configuration in the build.gradle file under the app module, as follows:

// Enable dataBinding   
dataBinding {
    enabled = true
}

Layout file configuration#

The Data Binding Library will automatically generate the classes needed to bind views and data objects in the layout. The root tag of the layout file for the Data Binding Library is the layout tag, followed by specific data elements and view elements. This view element is the location of the binding layout file. The layout file is referenced as follows:

<?xml version="1.0" encoding="utf-8"?>
<!--dataBinding must use layout as the root tag-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!--Data object-->
    <data>
        <variable name="user" type="com.manu.databindsample.data.User"/>
    </data>
    <!--View elements-->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!--The specific property value configuration in dataBinding is done in "@{}"-->
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name,default=Name}"/>
    </LinearLayout>
</layout>

Data entity#

In the "@{user.name}" expression, the name property ultimately maps to the getter method of the data object, which is the getter method. Of course, if there is a corresponding name method in the data object, it will call the method with the same name when there is no corresponding getter method. If both exist, the corresponding getter method will be called first, as referenced below:

/**
 * Data entity
 * Powered by jzman.
 * Created on 2018/11/28 0028.
 */
public class User {
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }
    
    // If both exist, the getter method is called first
    public String getName() {
        return name;
    }
    // The method will be called if the getter method does not exist
    public String name() {
        return "name";
    }

    //...
}

Binding data#

dataBinding will generate the corresponding binding class for the layout file. By default, the class name is based on the layout file name. For example, if the layout file name is activity_main, the corresponding binding class for that layout file is ActivityMainBinding. This class contains all the bindings from the data object to the layout file. So how do we bind data and views? In the Activity, the binding method is as follows:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Generate binding class
        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        // Bind view and data
        User user = new User("Zhang San");
        binding.setUser(user);
    }
}

In the Fragment, the binding method is as follows:

// inflate method
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    FragmentOneBinding oneBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_one,container,false);
    User user = new User("Xiao Ming");
    oneBinding.setUser(user);
    return oneBinding.getRoot();
}
// bind method
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
    View view = inflater.inflate(R.layout.fragment_one,container,false);
    FragmentOneBinding oneBinding = FragmentOneBinding.bind(view);
    User user = new User("Xiao Ming");
    oneBinding.setUser(user);
    return view;
}

The binding methods for other layouts are generally completed using the inflate method and bind method of a generated binding class.

Special expressions#

  • Ternary operator simplification
// Full syntax
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
// Short syntax
android:text="@{user.displayName ?? user.lastName}"
  • Null pointer exception handling

The generated binding class will automatically check for null values to avoid NullPointerException. In the expression @ {user.name}, if user is null, it assigns the default value null to user.name. If referencing user.age, where age is of type int, the data binding uses the default value 0.

  • Collections
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="java.util.Map" />

        <import type="java.util.List" />

        <!--Map-->
        <variable
            name="map"
            type="Map&lt;String,String>" />

        <variable
            name="key"
            type="String" />

        <!--List-->
        <variable
            name="list"
            type="List&lt;String>" />

        <variable
            name="index"
            type="int" />
    </data>
    <!--Note the way to access Map and List-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{map.key}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list[0]}" />

    </LinearLayout>
</layout>

For Map type data, you can use map.key in the expression @{} to get the value corresponding to the key in the Map collection. For List type data, you can directly use the index to access the value. Additionally, in the variable tag, the < character must be escaped using < instead of <, otherwise, the following error will occur:

> Error: The "type" attribute value associated with the element type "variable" cannot contain the '<' character.
  • Using strings in @{} expressions

How to use strings instead of string variables in @{} expressions? There are two ways, as follows:

<!--Using single quotes-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{map["key"]}' />
<!--Using backticks-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{map[`key`]}" />
<!--You can also use string resources in @{}-->
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{@string/app_name}"/>

Event binding#

When using dataBinding, you can set event listeners using method references or listener bindings. The difference between the two is that the former creates the event listener at the time of data binding, while the latter binds when the event is triggered.

  • Method reference

Events can be directly bound to event handling methods. Compared to the ordinary android attribute, this configuration method will perform related processing at compile time. If the method does not exist or the method signature is incorrect, a compile-time error will be received. First, create an event handling method as follows:

/**
 * Powered by jzman.
 * Created on 2018/11/30 0030.
 */
public class MyHandler {
    /**
     * @param view
     */
    public void onClickEvent(View view){
        Toast.makeText(view.getContext(), "click me", Toast.LENGTH_SHORT).show();
    }
}

Then, configure specific onClick and other events in the corresponding layout file. Here is an example for the onClick event:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="handler"
            type="com.manu.databindsample.handler.MyHandler"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <!--First method-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler::onClickEvent}"/>
        <!--Second method-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler.onClickEvent}"/>
    </LinearLayout>
</layout>

Finally, set the data object handler in the corresponding Activity, as referenced below:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setHandler(new MyHandler());
}

This successfully binds the event through method reference.

  • Listener binding

This method creates the event listener when the event occurs. Compared to method references, it can pass custom parameters in the event callback. First, create an event callback method as follows:

/**
 * Listener binding
 * Powered by jzman.
 * Created on 2018/12/3 0003.
 */
public class MyPresenter {
    private Context mContext;

    public MyPresenter(Context mContext) {
        this.mContext = mContext;
    }

    public void onClickEvent(User user) {
        Toast.makeText(mContext, user.getName(), Toast.LENGTH_SHORT).show();
    }
}

Then, configure specific onClick and other events in the corresponding layout file. Here is an example for the onClick event:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.manu.databindsample.data.User" />

        <variable
            name="presenter"
            type="com.manu.databindsample.handler.MyPresenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onClickEvent(user)}"
            android:text="click me 3" />
    </LinearLayout>
</layout>

Finally, set the data object presenter in the corresponding Activity, as referenced below:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setUser(new User("android"));
    binding.setPresenter(new MyPresenter(this));
}

This successfully binds the event through event listener. In the above XML, when calling the event method, you can configure the current View, as follows:

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{(view) -> presenter.onClickEvent(user)}"
    android:text="click me 3" />

The corresponding event callback method is as follows:

public class MyPresenter {
    public void onClickEvent(View view, User user){}
}

Additionally, you can use the ternary operator when binding events, where void can be used as an operator. The usage is as follows:

android:onClick="@{(v) -> v.isVisible() ? presenter.doSomething() : void}"

Custom binding classes#

As previously mentioned, the default binding class name is determined by the layout file name. So how do you customize the binding class? You can specify the custom binding class name using the class attribute on the data tag of the layout file. You can also add the complete package path before the custom class name, as referenced below:

<!--Custom binding class-->
<data class="com.manu.test.CustomBinding">
    <variable name="user" type="com.manu.databindsample.data.User"/>
</data>

Others#

In dataBinding, you can use the import keyword to import related classes. Classes under java.lang.* are automatically imported by default. If there are View classes with the same name, you can use alias to distinguish them, as referenced below:

<import type="android.view.View"/>
<import type="com.manu.View"
        alias="MView"/>

Use the variable keyword to define variables to be used in the XML layout. If you use an include layout, you need to use bind to bind the included layout with the main layout using the same variables. Create an include layout file named test_layout.xml, as follows:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="userTest"
            type="com.manu.databindsample.data.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`this is include content...`+userTest.getName(),default=user}" />
    </LinearLayout>
</layout>

Then, reference this layout in the main layout, as follows:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="user"
            type="com.manu.databindsample.data.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
         <!--bind binds variables-->
        <include
            layout="@layout/test_layout"
            bind:userTest="@{user}" />
    </LinearLayout>
</layout>

This binds the User type variables used in both layouts through bind, ensuring that the variables used in both layouts are the same. Additionally, dataBinding does not support merge tags. The next article will continue with the introduction of Binding adapters.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.