Data Binding addresses a significant pain point in Android UI development by providing native support for the MVVM architectural pattern. This framework enables seamless integration between the UI layer and data layer without requiring major refactoring of existing code.
This approach may gradually diminish the relevance of dependency injection libraries like RoboGuice and ButterKnife, as direct view variable references in Java code become increasingly rare.
Getting Started
Project Configuration
Ensure the Android Gradle plugin version is at least 1.5.0-alpha1 in your project-level build.gradle:
classpath 'com.android.tools.build:gradle:1.5.0'
Enable data binding in the module-level build.gradle file:
dataBinding {
enabled true
}
Fundamentals
Layout Configuration
With Data Binding enabled, layout XML files serve dual purposes: UI rendering and variable definition. The root element transforms from a ViewGroup to a layout container, introducing a new data section:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<LinearLayout>
...
</LinearLayout>
</layout>
The data element acts as a bridge connecting Views and Models, facilitating the MVVM ViewModel implementation by declaring variables that provide data to UI elements.
Data Model
Create a straightforward POJO class representing a user entity:
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Variable Declaration
Define a User variable within the data element of your layout file:
<data>
<variable name="user" type="com.example.databinding.User" />
</data>
The type attribute references the fully qualified class name defined in your Java code. The data node also supports imports:
<data>
<import type="com.example.databinding.User" />
<variable name="user" type="User" />
</data>
The binding plugin automatically gneerates a class extending ViewDataBinding based on the layout filename. For activity_main.xml, the generated class becomes ActivityMainBinding.
Note: Classes from java.lang.* are automatically imported and can be used directly.
Binding Variables in Code
Update the Activity's onCreate method to use DataBindingUtil.setContentView() instead of setContentView():
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(
this, R.layout.activity_main);
User user = new User("john", "doe");
binding.setUser(user);
}
Custom class names can be specified in the data element:
<data class="com.example.CustomBinding">
</data>
Note: Generated binding classes include setter methods derived from variable names. Declaring firstName and lastName variables generates corresponding setFirstName() and setLastName() methods.
Referencing Variables in Layouts
Once data is bound to variables, UI elements can directly access them:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
Advenced Techniques
Using Static Methods
Define utility methods in a helper class:
public class StringUtils {
public static String capitalize(final String text) {
if (text.length() > 1) {
return String.valueOf(text.charAt(0)).toUpperCase() + text.substring(1);
}
return text;
}
}
Import and invoke in the layout:
<import type="com.example.databinding.StringUtils" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{StringUtils.capitalize(user.firstName)}" />
Type Aliases
When importing multiple classes with identical names, use the alias attribute:
<import type="com.example.home.data.User" />
<import type="com.example.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
Null Coalescing Operator
android:text="@{user.displayName ?? user.lastName}"
Equivalent to:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
Dynamic Attribute Values
Bind properties directly from Java:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
Resource Binding
<TextView
android:padding="@{isLarge ? (int)@dimen/largePadding : (int)@dimen/smallPadding}"
android:background="@android:color/black"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
Observable Binding
Current Data Binding implementation supports one-way binding, not the bidirectional approach found in frameworks like Angular.js.
BaseObservable Implementation
Extend BaseObservable to create observable data models:
public class ObservableUser extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return firstName;
}
@Bindable
public String getLastName() {
return lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
The @Bindable annotation generates entries in the BR class. Calling notifyPropertyChanged(BR.firstName) signals the system to refresh UI elements bound to that property.
ObservableField Implementation
For field-level granularity without inheritance, use ObservableField:
public class PlainUser {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
Android provides Observable counterparts for primitive types: ObservableInt, ObservableFloat, ObservableBoolean, and ObservableField for reference types.
Views with IDs
Data Binding significantly reduces view reference code, but direct access remains available. Assigning an ID to a view generates a corresponding final field:
<TextView
android:id="@+id/firstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
The generated binding class contains:
public final TextView firstName;
ViewStub Integration
Layout ViewStub elements transform into ViewStubProxy objects when bound:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout>
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub" />
</LinearLayout>
</layout>
In Java code, register inflation listeners:
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
StubBinding inflatedBinding = DataBindingUtil.bind(inflated);
User user = new User("alice", "smith");
inflatedBinding.setUser(user);
}
});
Dynamic Variables in RecyclerView
For adapters requiring dynamic binding creation, instantiate the binding in onCreateViewHolder and retrieve it in onBindViewHolder:
public static class BindingHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public BindingHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
ViewDataBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.getContext()),
R.layout.list_item,
viewGroup,
false);
BindingHolder holder = new BindingHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
User user = users.get(position);
holder.getBinding().setVariable(BR.user, user);
holder.getBinding().executePendingBindings();
}
Alternative approach binding directly in the ViewHolder constructor:
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private static final int USER_COUNT = 10;
private final List<User> mUsers;
public UserAdapter() {
mUsers = new ArrayList<>(USER_COUNT);
for (int i = 0; i < USER_COUNT; i++) {
mUsers.add(new User(randomFirstName(), randomLastName()));
}
}
public static class UserHolder extends RecyclerView.ViewHolder {
private final UserItemBinding mBinding;
public UserHolder(View itemView) {
super(itemView);
mBinding = DataBindingUtil.bind(itemView);
}
public void bind(@NonNull User user) {
mBinding.setUser(user);
}
}
@Override
public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.user_item, viewGroup, false);
return new UserHolder(itemView);
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
holder.bind(mUsers.get(position));
}
@Override
public int getItemCount() {
return mUsers.size();
}
}
Custom Attribute Setters
Data Binding enables XML atribute assignment for custom views without declaring them in declare-styleable. As long as setter methods exist, binding works automatically:
<com.example.databinding.UserView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/largePadding"
app:onClickListener="@{activity.clickListener}"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}"
app:age="27" />
Binding Converters
Important: Converters may affect unrelated attributes. For instance, a
@BindingConversionmethod convertinginttointcould impactandroid:visibilityunexpectedly.
When attribute types don't match variable types, converters enable transformation:
<View
android:background="@{hasError.get() ? @color/red : @color/white}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_height="@{height}" />
Define a static @BindingConversion method:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}