Layouts and binding expressions
Stay organized with collections
Save and categorize content based on your preferences.
The expression language lets you write expressions that handle events dispatched by views. The Data Binding Library automatically generates the classes required to bind the views in the layout with your data objects.
Data binding layout files are slightly different and start with a root tag of
layout
, followed by a data
element and a view
root element. This view
element is what your root is in a non-binding layout file. The following code
shows a sample layout file:
<?xmlversion="1.0"encoding="utf-8"?>
<layoutxmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variablename="user"type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextViewandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextViewandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
The user
variable within data
describes a property that can be used within
this layout:
<variablename="user"type="com.example.User"/>
Expressions within the layout are written in the attribute properties using the
@{}
syntax. In the following example, the
TextView
text is set to the
firstName
property of the user
variable:
<TextViewandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
Data objects
Suppose you have a plain object to describe the User
entity:
Kotlin
dataclassUser(valfirstName:String,vallastName:String)
Java
publicclass User{ publicfinalStringfirstName; publicfinalStringlastName; publicUser(StringfirstName,StringlastName){ this.firstName=firstName; this.lastName=lastName; } }
This type of object has data that never changes. It's common in apps to have data that is read once and never changes thereafter. It's also possible to use an object that follows a set of conventions, such as using accessor methods in the Java programming language, as shown in the following example:
Kotlin
// Not applicable in Kotlin. dataclassUser(valfirstName:String,vallastName:String)
Java
publicclass User{ privatefinalStringfirstName; privatefinalStringlastName; publicUser(StringfirstName,StringlastName){ this.firstName=firstName; this.lastName=lastName; } publicStringgetFirstName(){ returnthis.firstName; } publicStringgetLastName(){ returnthis.lastName; } }
From the perspective of data binding, these two classes are equivalent. The
expression @{user.firstName}
used for the
android:text
attribute accesses the firstName
field in the former class and the
getFirstName()
method in the latter class. It is also resolved to
firstName()
, if that method exists.
Binding data
A binding class is generated for each layout file. By default, the name of the
class is based on the name of the layout file, converted to Pascal case, with
the Binding suffix added to it. For example, the preceding layout filename is
activity_main.xml
, so the corresponding generated binding class is
ActivityMainBinding
.
This class holds all the bindings from the layout properties—for example,
the user
variable—to the layout's views and knows how to assign values
for the binding expressions. We recommend creating the bindings while inflating
the layout, as shown in the following example:
Kotlin
overridefunonCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) valbinding:ActivityMainBinding=DataBindingUtil.setContentView( this,R.layout.activity_main) binding.user=User("Test","User") }
Java
@Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); ActivityMainBindingbinding=DataBindingUtil.setContentView(this,R.layout.activity_main); Useruser=newUser("Test","User"); binding.setUser(user); }
At runtime, the app displays the Test user in the UI. Alternatively, you can
get the view using a
LayoutInflater
, as shown in the
following example:
Kotlin
valbinding:ActivityMainBinding=ActivityMainBinding.inflate(getLayoutInflater())
Java
ActivityMainBindingbinding=ActivityMainBinding.inflate(getLayoutInflater());
If you are using data binding items inside a
Fragment
,
ListView
, or
RecyclerView
adapter, you might prefer to use the
inflate()
methods of the bindings classes or the
DataBindingUtil
class, as
shown in the following code example:
Kotlin
vallistItemBinding=ListItemBinding.inflate(layoutInflater,viewGroup,false) // or vallistItemBinding=DataBindingUtil.inflate(layoutInflater,R.layout.list_item,viewGroup,false)
Java
ListItemBindingbinding=ListItemBinding.inflate(layoutInflater,viewGroup,false); // or ListItemBindingbinding=DataBindingUtil.inflate(layoutInflater,R.layout.list_item,viewGroup,false);
Expression language
Common features
The expression language looks a lot like expressions found in managed code. You can use the following operators and keywords in the expression language:
- Mathematical:
+ - / * %
- String concatenation:
+
- Logical:
&& ||
- Binary:
& | ^
- Unary:
+ - ! ~
- Shift:
>> >>> <<
- Comparison:
== > < >= <=
(the<
needs to be escaped as<
) instanceof
- Grouping:
()
- Literals, such as character, String, numeric,
null
- Cast
- Method calls
- Field access
- Array access:
[]
- Ternary operator:
?:
Here are some examples:
android:text="@{String.valueOf(index+1)}"
android:visibility="@{age > 13?View.GONE:View.VISIBLE}"
android:transitionName='@{"image_"+id}'
Missing operations
The following operations are missing from the expression syntax that you can use in managed code:
this
super
new
- Explicit generic invocation
Null coalescing operator
The null coalescing operator (??
) chooses the left operand if it isn't null
or the right if the former is null
:
android:text="@{user.displayName??user.lastName}"
This is functionally equivalent to the following:
android:text="@{user.displayName!=null?user.displayName:user.lastName}"
Property references
An expression can reference a property in a class by using the following format,
which is the same for fields, getters, and
ObservableField
objects:
android:text="@{user.lastName}"
Avoid null pointer exceptions
Generated data binding code automatically checks for null
values and avoids
null pointer exceptions. For example, in the expression @{user.name}
, if
user
is null, user.name
is assigned its default value of null
. If you
reference user.age
, where age is of type int
, then data binding uses the
default value of 0
.
View references
An expression can reference other views in the layout by ID, using the following syntax:
android:text="@{exampleText.text}"
In the following example, the TextView
view references an EditText
view in
the same layout:
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
Collections
You can access common collections, such as arrays, lists, sparse lists, and
maps, using the []
operator for convenience.
<data>
<importtype="android.util.SparseArray"/>
<importtype="java.util.Map"/>
<importtype="java.util.List"/>
<variablename="list"type="List<String>"/>
<variablename="sparse"type="SparseArray<String>"/>
<variablename="map"type="Map<String,String>"/>
<variablename="index"type="int"/>
<variablename="key"type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"
You can also refer to a value in the map using the object.key
notation. For
example, you can replace @{map[key]}
in the preceding example with
@{map.key}
.
String literals
You can use single quotes to surround the attribute value, which lets you use double quotes in the expression, as shown in the following example:
android:text='@{map["firstName"]}'
You can also use double quotes to surround the attribute value. When doing so,
string literals must be surrounded with backticks `
, as shown
here:
android:text="@{map[`firstName`]}"
Resources
An expression can reference app resources with the following syntax:
android:padding="@{large?@dimen/largePadding:@dimen/smallPadding}"
You can evaluate format strings and plurals by providing parameters:
android:text="@{@string/nameFormat(firstName,lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
You can pass property references and view references as resource parameters:
android:text="@{@string/example_resource(user.lastName,exampleText.text)}"
When a plural takes multiple parameters, pass all parameters:
Haveanorange
Have%doranges
android:text="@{@plurals/orange(orangeCount,orangeCount)}"
Some resources require explicit type evaluation, as shown in the following table:
Type | Normal reference | Expression reference |
---|---|---|
String[] |
@array |
@stringArray |
int[] |
@array |
@intArray |
TypedArray |
@array |
@typedArray |
Animator |
@animator |
@animator |
StateListAnimator |
@animator |
@stateListAnimator |
color int |
@color |
@color |
ColorStateList |
@color |
@colorStateList |
Event handling
Data binding lets you write expression handling events that are dispatched from
the views—for example, the
onClick()
method. Event attribute names are determined by the name of the listener method,
with a few exceptions. For example,
View.OnClickListener
has
a method onClick()
, so the attribute for this event is android:onClick
.
There are some specialized event handlers for the click event that need an
attribute other than android:onClick
to avoid a conflict. You can use the
following attributes to avoid these type of conflicts:
Class | Listener setter | Attribute |
---|---|---|
SearchView |
setOnSearchClickListener(View.OnClickListener) |
android:onSearchClick |
ZoomControls |
setOnZoomInClickListener(View.OnClickListener) |
android:onZoomIn |
ZoomControls |
setOnZoomOutClickListener(View.OnClickListener) |
android:onZoomOut |
You can use these two mechanisms, described in detail in the sections that follow, to handle an event:
- Method references: in your expressions, you can
reference methods that conform to the signature of the listener method. When
an expression evaluates to a method reference, data binding wraps the method
reference and owner object in a listener and sets that listener on the
target view. If the expression evaluates to
null
, data binding doesn't create a listener and sets anull
listener instead. - Listener bindings: these are lambda expressions that are evaluated when the event happens. Data binding always creates a listener, which it sets on the view. When the event is dispatched, the listener evaluates the lambda expression.
Method references
You can bind events to handler methods directly, similar to the way you can
assign
android:onClick
to a
method in an activity. One advantage compared to the
View
onClick
attribute is that the
expression is processed at compile time. So, if the method doesn't exist or its
signature is incorrect, you receive a compile time error.
The major difference between method references and listener bindings is that the actual listener implementation is created when the data is bound, not when the event is triggered. If you prefer to evaluate the expression when the event happens, use listener bindings.
To assign an event to its handler, use a normal binding expression, with the value being the method name to call. For example, consider the following example layout data object:
Kotlin
classMyHandlers{ funonClickFriend(view:View){...} }
Java
publicclass MyHandlers{ publicvoidonClickFriend(Viewview){...} }
The binding expression can assign the click listener for a view to the
onClickFriend()
method, as follows:
<?xmlversion="1.0"encoding="utf-8"?>
<layoutxmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variablename="handlers"type="com.example.MyHandlers"/>
<variablename="user"type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextViewandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
Listener bindings
Listener bindings are binding expressions that run when an event happens. They are similar to method references, but they let you run arbitrary data binding expressions. This feature is available with Android Gradle Plugin for Gradle version 2.0 and later.
In method references, the parameters of the method must match the parameters of
the event listener. In listener bindings, only your return value must match the
expected return value of the listener, unless it is expecting void
. For
example, consider the following presenter class that has an onSaveClick()
method:
Kotlin
classPresenter{ funonSaveClick(task:Task){} }
Java
publicclass Presenter{ publicvoidonSaveClick(Tasktask){} }
You can bind the click event to the onSaveClick()
method as follows:
<?xmlversion="1.0"encoding="utf-8"?>
<layoutxmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variablename="task"type="com.android.example.Task"/>
<variablename="presenter"type="com.android.example.Presenter"/>
</data>
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent">
<Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"
android:onClick="@{()->presenter.onSaveClick(task)}"/>
</LinearLayout>
</layout>
When a callback is used in an expression, data binding automatically creates the necessary listener and registers it for the event. When the view fires the event, data binding evaluates the given expression. As with regular binding expressions, you get the null and thread safety of data binding while these listener expressions are being evaluated.
In the preceding example, the view
parameter that is passed to onClick(View)
isn't defined. Listener bindings provide two choices for listener parameters:
you can ignore all parameters to the method or name all of them. If you prefer
to name the parameters, you can use them in your expression. For example, you
can write the preceding expression as follows:
android:onClick="@{(view)->presenter.onSaveClick(task)}"
If you want to use the parameter in the expression, you can do that as follows:
Kotlin
classPresenter{ funonSaveClick(view:View,task:Task){} }
Java
publicclass Presenter{ publicvoidonSaveClick(Viewview,Tasktask){} }
android:onClick="@{(theView)->presenter.onSaveClick(theView,task)}"
And you can use a lambda expression with more than one parameter:
Kotlin
classPresenter{ funonCompletedChanged(task:Task,completed:Boolean){} }
Java
publicclass Presenter{ publicvoidonCompletedChanged(Tasktask,booleancompleted){} }
<CheckBoxandroid:layout_width="wrap_content"android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb,isChecked)->presenter.completeChanged(task,isChecked)}"/>
If the event you are listening to returns a value whose type isn't void
, your
expressions must return the same type of value as well. For example, if you want
to listen for the touch & hold (long click) event, your expression must return a
boolean.
Kotlin
classPresenter{ funonLongClick(view:View,task:Task):Boolean{} }
Java
publicclass Presenter{ publicbooleanonLongClick(Viewview,Tasktask){} }
android:onLongClick="@{(theView)->presenter.onLongClick(theView,task)}"
If the expression can't be evaluated due to null
objects, data binding returns
the default value for that type, such as null
for reference types, 0
for
int
, or false
for boolean
.
If you need to use an expression with a predicate—for example, a
ternary—you can use void
as a symbol:
android:onClick="@{(v)->v.isVisible()?doSomething():void}"
Avoid complex listeners
Listener expressions are powerful and can make your code easier to read. On the other hand, listeners containing complex expressions make your layouts harder to read and maintain. Keep your expressions as simple as passing available data from your UI to your callback method. Implement any business logic inside the callback method that you invoke from the listener expression.
Imports, variables, and includes
The Data Binding Library provides features such as imports, variables, and includes. Imports make easy-to-reference classes inside your layout files. Variables let you describe a property that can be used in binding expressions. Includes let you reuse complex layouts across your app.
Imports
Imports let you reference classes inside your layout file, like in managed code.
You can use zero or more import
elements inside the data
element. The
following code example imports the View
class to the layout file:
<data>
<importtype="android.view.View"/>
</data>
Importing the View
class lets you reference it from your binding expressions.
The following example shows how to reference the
VISIBLE
and
GONE
constants of the View
class:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult?View.VISIBLE:View.GONE}"/>
Type aliases
When there are class name conflicts, you can rename one of the classes to an
alias. The following example renames the View
class in the
com.example.real.estate
package to Vista
:
<importtype="android.view.View"/>
<importtype="com.example.real.estate.View"
alias="Vista"/>
You can then use Vista
to reference com.example.real.estate.View
and View
to reference android.view.View
within the layout file.
Import other classes
You can use imported types as type references in variables and expressions. The
following example shows User
and List
used as the type of a variable:
<data>
<importtype="com.example.User"/>
<importtype="java.util.List"/>
<variablename="user"type="User"/>
<variablename="userList"type="List<User>"/>
</data>
You can use the imported types to cast part of an expression. The following
example casts the connection
property to a type of User
:
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
You can also use imported types when referencing static fields and methods in
expressions. The following code imports the MyStringUtils
class and references
its capitalize
method:
<data>
<importtype="com.example.MyStringUtils"/>
<variablename="user"type="com.example.User"/>
</data>
...
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Just as in managed code, java.lang.*
is imported automatically.
Variables
You can use multiple variable
elements inside the data
element. Each
variable
element describes a property that can be set on the layout to be used
in binding expressions within the layout file. The following example declares
the user
, image
, and note
variables:
<data>
<importtype="android.graphics.drawable.Drawable"/>
<variablename="user"type="com.example.User"/>
<variablename="image"type="Drawable"/>
<variablename="note"type="String"/>
</data>
The variable types are inspected at compile time, so if a variable implements
Observable
or is an
observable collection,
that must be reflected in the type. If the variable is a base class or interface
that doesn't implement the Observable
interface, the variables aren't
observed.
When there are different layout files for various configurations (for example, landscape or portrait), the variables are combined. There must not be conflicting variable definitions between these layout files.
The generated binding class has a setter and getter for each of the described
variables. The variables take the default managed code values until the setter
is called—null
for reference types, 0
for int
, false
for
boolean
, etc.
A special variable named context
is generated for use in binding expressions
as needed. The value for context
is the
Context
object from the root view's
getContext()
method. The
context
variable is overridden by an explicit variable declaration with that
name.
Includes
You can pass variables into an included layout's binding from the containing
layout by using the app namespace and the variable name in an attribute. The
following example shows included user
variables from the name.xml
and
contact.xml
layout files:
<?xmlversion="1.0"encoding="utf-8"?>
<layoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variablename="user"type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<includelayout="@layout/name"
bind:user="@{user}"/>
<includelayout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
Data binding doesn't support an include as a direct child of a merge element. For example, the following layout isn't supported:
<?xmlversion="1.0"encoding="utf-8"?>
<layoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variablename="user"type="com.example.User"/>
</data>
<merge><!--Doesn'twork-->
<includelayout="@layout/name"
bind:user="@{user}"/>
<includelayout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
Additional resources
To learn more about data binding, consult the following additional resources.