Using data binding in Android
This tutorial describes the usage of data binding in Android applications. Data binding allows to synchronize your user interface with your application model and logic.
1. Using data binding in Android applications
1.1. Introduction to data binding in Android
Android offers support to write declarative layouts using data binding. This minimizes the necessary code in your application logic to connect to the user interface elements.
The usage of data binding requires changes in your layout files. Such layout files starts with a layout
root tag followed by a data
element and a view
root element. The data elements describe data which is available for binding. This view
element contains your root hierarchy similar to layout files which are not used with data binding. References to the data elements or expressions within the layout are written in the attribute properties using the @{}
or @={}
,
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="temp" type="com.vogella.android.databinding.TemperatureData"/> **(1)**
</data>
<LinearLayout **(2)**
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{temp.location}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{temp.celsius}"/>
</LinearLayout>
</layout>
- 1 The user variable within data describes a property that may be used within this layout.
- 2 Normal view hierarchy
Android data binding generates a Binding
class based on this layout. This class holds all the bindings from the layout properties, i.e., the defined variable to the corresponding views. It also provides generated setters for your data elements from the layout. The name of the generated class is based on the name of the layout file. This name is converted to Pascal case and the Binding
suffix is added to it. For example, if the layout file is called activity_main.xml
, the generate class is called ActivityMainBinding
. You can inflate the layout and connect your model via this class or the DataBindingUtil
class.
TemperatureData temperatureData = // your data is created here
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setTemp(temperatureData); // generated setter based on the data in the layout file
You can use the inflate
method on the generated class. This is useful for using data binding in fragments, ListView
or RecyclerView
.
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater(), container, attachToContainer);
// get the root view
View view = binding.getRoot();
// do more stuff
TemperatureData temperatureData = // your data is created here
binding.setTemp(temperatureData); // generated setter based on the data in the layout file
You can also inflate layouts for RecyclerView
, ViewPager
, or other things that aren’t setting the Activity contents.
1.2. Enable data binding in your Android application
To enable the usage of data binding in your Android application, add the following snippet to the app/build.gradle file.
1.3. Data binding for events via listener bindings and method references
Events may be bound to handler methods directly, similar to the way android:onClick
can be assigned to a method in the activity. Event attribute names are governed by the name of the listener method with a few exceptions. For example, View.OnLongClickListener
has a method onLongClick()
, so the attribute for this event is android:onLongClick
.
To assign an event to its handler, use a normal binding expression, with the value being the method name to call. The binding expression can assign the click listener for a View.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="presenter"
type="com.vogella.android.databinding.MainActivityPresenter"/>
</data>
<Button
android:text="Start second activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.showList()}"
/>
</layout>
You could also bind to a method reference via android:onClick="@{handlers::onClickFriend}"/>
. If you methods need parameters, you can also pass your data object to them. For example:
1.4. Imports
You can also import classes to use them in your data binding expressions.
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
1.5. Updating the user interfaces with changes from the data model
Any plain old Java object (POJO) can be used for data binding. But if updates in the data model should also update the user interface, the objects must be able to notify about data changes. There are three different data change notification mechanisms: * observable objects * observable fields * observable collections
Android provides the BaseObservable
class which you can extend. The data class is responsible for notifying when the properties change. This is done by assigning a @Bindable
annotation to the getter and notifying in the setter.
package com.vogella.android.databinding;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import java.util.Observable;
public class TemperatureData extends BaseObservable {
private String celsius;
public TemperatureData(String celsius) {
this.celsius = celsius;
}
@Bindable **(1)**
public String getCelsius() {
return celsius;
}
public void setCelsius(String celsius) {
this.celsius = celsius;
notifyPropertyChanged(BR.celsius); **(2)**
}
}
- 1 Define a relevant getter
- 2 Notify any listeners, BR.celsius is a generated class
This listener is invoked on every update and it updates the corresponding views. This ensures that updates in the model updates also the UI.
Alternatively to create a observable class, you can also use ObservableField
and its subclass for properties.
private class TemperatureData {
public final ObservableField<String> celsius = new ObservableField<>();
public final ObservableField<String> location = new ObservableField<>();
}
To access such fields in your code, use the set
and get
methods.
1.6. Custom converters with BindingAdapter
Sometimes you have to perform complex data conversions. For this, you can register a custom converter via the static @BindingAdapter
method. This method can be placed anywhere in your code and can override the default conversion of a field to your data model.
For example, assume that you want to assign a field of your data model to an image view.
<ImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="@{obj.url}"
/>
You can register for this property on ImageView
with the following method. This method uses Glide to download the image.
@BindingAdapter("android:src")
public static void setImageUrl(ImageView view, String url) {
Glide.with(view.getContext()).load(url).into(view);
}
googletag.cmd.push(function() { googletag.display('ad-inBetween01'); });
2. Exercise: Using data binding in Android applications
In this exercise you learn how to interact between your user interface widgets using data binding. Create a new Android application for this exercise, with the com.vogella.android.databinding
top level package. Use the Empty template for this purpose.
2.1. Activate the usage of data binding
Open your app/build.gradle file and activate the usage of data binding.
apply plugin: 'com.android.application'
android {
dataBinding {
enabled = true
}
.... [REST AS BEFORE...]
- Ensure you pick the correct build file.
- This setting must be done in the build.gradle file of your app.
2.2. Create classes for the view interaction
Create the following classes.
package com.vogella.android.databinding;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class TemperatureData extends BaseObservable {
private String location;
private String celsius;
public TemperatureData(String location, String celsius) {
this.location = location;
this.celsius = celsius;
}
@Bindable
public String getCelsius() {
return celsius;
}
@Bindable
public String getLocation() {
return location;
}
public void setLocation(String location){
this.location = location;
notifyPropertyChanged(BR.location);
}
public void setCelsius(String celsius) {
this.celsius = celsius;
notifyPropertyChanged(BR.celsius);
}
}
- The BR class is not yet generated.
- After the definition of the layout file, the is will be generated by the Gradle tooling.
package com.vogella.android.databinding;
public interface MainActivityContract {
public interface Presenter {
void onShowData(TemperatureData temperatureData);
}
public interface View {
void showData(TemperatureData temperatureData);
}
}
package com.vogella.android.databinding;
import android.content.Context;
public class MainActivityPresenter implements MainActivityContract.Presenter {
private MainActivityContract.View view;
private Context ctx;
public MainActivityPresenter(MainActivityContract.View view, Context ctx) {
this.view = view;
this.ctx = ctx;
}
@Override
public void onShowData(TemperatureData temperatureData) {
view.showData(temperatureData);
}
}
2.3. Adjust layout file and activity to use data binding
Change the layout to the following.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="temp"
type="com.vogella.android.databinding.TemperatureData" />
<variable
name="presenter"
type="com.vogella.android.databinding.MainActivityPresenter"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={temp.location}"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={temp.celsius}"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@={temp.celsius}" />
<Button
android:text="Show data model"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onShowData(temp)}"
android:id="@+id/button" />
</LinearLayout>
</layout>
- You see some warning messages in the editor, e.g., because you used hard-codes strings.
- For this exercise, we ignore these warnings.
Adjust your activity code to use the generated data binding classes.
package com.vogella.android.databinding;
import android.app.Activity;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.widget.Toast;
import com.vogella.android.databinding.databinding.ActivityMainBinding;
public class MainActivity extends Activity implements MainActivityContract.View {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
MainActivityPresenter mainActivityPresenter = new MainActivityPresenter(this, getApplicationContext());
TemperatureData temperatureData = new TemperatureData("Hamburg", "10");
binding.setTemp(temperatureData);
binding.setPresenter(mainActivityPresenter);
}
@Override
public void showData(TemperatureData temperatureData) {
String celsius = temperatureData.getCelsius();
Toast.makeText(this, celsius, Toast.LENGTH_SHORT).show();
}
}
2.4. Convince Android Studio to compile your application
If the BR class is missing, select Build Clean followed by Build Make Project.
2.5. Validate your application
Start your application. If you press the button, a small popup should be shown with the correct data.
The first text view should The second TextView
should update automatically, if you type in the EditText
field.
3. Exercise: Using data binding for RecyclerView
In this exercise you learn how to use data binding for a recyclerview. Continue to use the com.vogella.android.databinding
package.
3.1. Define a new activity and allow to start it
Create a new activity called SecondActivity
. Ensure that you add it to your Android manifest.
Adjust your MVP contract to start the second activity.
package com.vogella.android.databinding;
public interface MainActivityContract {
public interface Presenter {
void onShowData(TemperatureData temperatureData);
void showList();
}
public interface View {
void showData(TemperatureData temperatureData);
}
}
Implement this new behavior in MainActivityPresenter
to start the second activity.
package com.vogella.android.databinding;
import android.content.Context;
import android.content.Intent;
public class MainActivityPresenter implements MainActivityContract.Presenter {
private MainActivityContract.View view;
private Context ctx;
public MainActivityPresenter(MainActivityContract.View view, Context ctx) {
this.view = view;
this.ctx = ctx;
}
@Override
public void onShowData(TemperatureData temperatureData) {
view.showData(temperatureData);
}
@Override
public void showList() {
Intent i = new Intent(ctx, SecondActivity.class);
ctx.startActivity(i);
}
}
3.2. Adjust layout file and activity to use data binding
Change the layout to the following.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="temp"
type="com.vogella.android.databinding.TemperatureData" />
<variable
name="presenter"
type="com.vogella.android.databinding.MainActivityPresenter"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={temp.location}"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={temp.celsius}"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@={temp.celsius}" />
<Button
android:text="Show data model"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onShowData(temp)}"
android:id="@+id/button" />
<Button
android:text="Start second activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.showList()}"
/>
</LinearLayout>
</layout>
3.3. Add dependency to RecyclerView
Open your app/build.gradle file and add the dependency to recyclerview.
- Ensure you pick the correct build file.
- You need to update the app build file.
3.4. Create icon
Add an ic_listentry icon to your application.
3.5. Implement the Recyclerview with data binding.
Create the following layout called activity_second.xml
.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="temp"
type="com.vogella.android.databinding.TemperatureData" />
<variable
name="presenter"
type="com.vogella.android.databinding.MainActivityPresenter"/>
</data>
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</layout>
Create the following layout called rowlayout.xml
.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="obj"
type="com.vogella.android.databinding.TemperatureData"
/>
</data>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip"
>
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="@drawable/ic_listentry"
/>
<TextView
android:id="@+id/secondLine"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/icon"
android:ellipsize="marquee"
android:text="@{obj.location}"
android:textSize="12sp"
android:maxLines="1"
/>
<TextView
android:id="@+id/firstLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/icon"
android:gravity="center_vertical"
android:text="@{obj.celsius}"
android:textSize="16sp"
/>
</RelativeLayout>
</layout>
Create the following adapter.
package com.vogella.android.databinding;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.vogella.android.databinding.databinding.RowlayoutBinding;
import java.util.List;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<TemperatureData> data;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class MyViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
private final ViewDataBinding binding;
public MyViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Object obj) {
binding.setVariable(BR.obj,obj);
binding.executePendingBindings();
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(List<TemperatureData> myDataset) {
data = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.rowlayout, parent, false);
// set the view's size, margins, paddings and layout parameters
return new MyViewHolder(binding);
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final TemperatureData temperatureData = data.get(position);
holder.bind(temperatureData);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return data.size();
}
}
Update your SecondActivity
class.
package com.vogella.android.databinding;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.Arrays;
import java.util.List;
public class SecondActivity extends Activity {
private RecyclerView recyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager layoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true);
// use a linear layout manager
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
List<TemperatureData> items =
Arrays.asList(new TemperatureData("Hamburg", "5"), new TemperatureData("Berlin", "6"));
// define an adapter
mAdapter = new MyAdapter(items);
recyclerView.setAdapter(mAdapter);
}
}
3.6. Validate your application
Start your application and navigate to your second activity. Ensure the list is correctly displayed.
3.7. Optional exercise: Create an abstract class for your adapter
Most of code in adapter can be the same if data binding is used. The only requirement is that the object name in the layout file is the same, so that the generated entry in the BR
class is the same. In our example we use obj
for it.
To reuse most of our adapter, create an abstract class with the logic to bind to any object.
package com.vogella.android.databinding;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.vogella.android.databinding.databinding.RowlayoutBinding;
public abstract class MyBaseAdapter extends RecyclerView.Adapter<MyBaseAdapter.MyViewHolder> {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class MyViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
private final ViewDataBinding binding;
public MyViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Object obj) {
binding.setVariable(BR.obj,obj);
binding.executePendingBindings();
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, getLayoutIdForType(viewType), parent, false);
// set the view's size, margins, paddings and layout parameters
return new MyViewHolder(binding);
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.bind(getDataAtPosition(position));
}
public abstract Object getDataAtPosition(int position);
public abstract int getLayoutIdForType(int viewType);
}
Now you can adjust your existing adapter to extend the MyBaseAdapter
.
package com.vogella.android.databinding;
import java.util.List;
public class MyAdapter extends MyBaseAdapter {
List<TemperatureData> data;
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(List<TemperatureData> myDataset) {
data = myDataset;
}
@Override
public Object getDataAtPosition(int position) {
return data.get(position);
}
@Override
public int getLayoutIdForType(int viewType) {
return R.layout.rowlayout;
}
@Override
public int getItemCount() {
return data.size();
}
}
- Having this base adapter allows you to reuse lots of code for new adapter implementations.
3.8. Optional exercise: Use a custom converter
Add a field url
to your data model and use Glide to download such an image. Examples URLs for images can be found on http://lorempixel.com/.
Add the following dependency to your app/build.gradle file.
To allow Glide to download from the Internet, add the permission to use the Internet to your manifest.
To register a custom converter, define the following static method, either in your activity or in a separate class.
@BindingAdapter("android:src")
public static void setImageUrl(ImageView view, String url) {
Glide.with(view.getContext()).
load(url).
placeholder(R.drawable.ic_listentry).
into(view);
}
Now change your data model.
package com.vogella.android.databinding;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class TemperatureData extends BaseObservable {
private String location;
private String celsius;
private String url;
public TemperatureData(String location, String celsius, String url) {
this.location = location;
this.celsius = celsius;
this.url = url;
}
@Bindable
public String getCelsius() {
return celsius;
}
@Bindable
public String getLocation() {
return location;
}
@Bindable
public String getUrl() {
return url;
}
public void setLocation(String location) {
this.location = location;
notifyPropertyChanged(BR.location);
}
public void setCelsius(String celsius) {
this.celsius = celsius;
notifyPropertyChanged(BR.celsius);
}
public void setUrl(String url) {
this.url = url;
notifyPropertyChanged(BR.url);
}
}
Also adjust the construction of the data model elements. For example, for the list.
List<TemperatureData> items =
Arrays.asList(
new TemperatureData("Hamburg", "5", "http://lorempixel.com/40/40/"),
new TemperatureData("Berlin", "6","http://lorempixel.com/40/40/"));
Adjust the android.src
attribute in your rowlayout.xml
.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="obj"
type="com.vogella.android.databinding.TemperatureData"
/>
</data>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip"
>
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="@{obj.url}"
/>
<TextView
android:id="@+id/secondLine"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/icon"
android:ellipsize="marquee"
android:text="@{obj.location}"
android:textSize="12sp"
android:maxLines="1"
/>
<TextView
android:id="@+id/firstLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/icon"
android:gravity="center_vertical"
android:text="@{obj.celsius}"
android:textSize="16sp"
/>
</RelativeLayout>
</layout>
googletag.cmd.push(function() { googletag.display('ad-afterResources'); });
4. Android databinding resources
Blog about about custom setters in Data binding
George Mount about Data Binding and RecyclerView
Dynamic view generation with Data Binding
Video introduction into Data Binding
原文链接: https://www.vogella.com/tutorials/AndroidDatabinding/article.html