Post on 07-Jan-2017
Android data binding
The rules of the game have changed
20 yrs tinkering with computersA lot of time on software localizationOn Android since 2010 (Cupcake)Mobile R+D Lead at Worldine IberiaAndroid GDE (Google Developer Expert)I love making UIProud parent of a 8 years old OS fan
@sergiandreplace
sergiandreplace.comsergi.Martinez[at]gmail.com
About me – Sergi Martínez
Android data binding
Introduced by Google in I/O 2015 (almost unnoticed)
But really important. It will change the way we make UIs
Google dixit
Writing declarative layouts and minimize the glue code necessary to
bind your application logic and layouts.
Data Binding library is for...
What is data binding?
Data binding is the process that establishes a connection between the application UI (User Interface) and Business logic. If the settings and notifications are correctly set, the data reflects changes when made. It can also mean that when the UI is changed, the underlying data will reflect that change
Wikipedia – Data binding
But before…
Let’s talk about inflation…
What’s inflation?
Inflation is the process used by Android to transform XML layouts into
a tree of View objects.
z
Inflation process
Inflate as
Or also performed inside setContentView
LayoutInflater.from(this).inflate(R.layout.activity_main, rootView);
public class MainActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }
}
Steps on inflation
1. The inflater parses the xml file
2. The inflater tries to create an object as:a. android.widget.<tag>b. android.webkit.<tag>c. <tag>
3. If succeds: creates the objects and sets the right propertiesIf fails: hell on earth
z
Some code (using custom inflater)
New inflater defined as InflaterFactory
z
Some code (using custom inflater)
New inflater defined as InflaterFactory
z
Some code (using custom inflater)
Once we set a new InflatorFactory, we are ready for inflation
z
More code (hacking the code inflater)
z
More code (hacking the code inflater)
z
More code (hacking the code inflater)
z
More code (hacking the code inflater)
z
More code (hacking the code inflater)
Custom code
The whole example
Check it out on https://
github.com/sergiandreplace/AndroidFontInflaterFactory(old Eclipse structure)
Also an experiment, use at your own risk
And now…
Let’s start with data binding…
…but…
WARNING
Data binding is in beta state
Use at your own risk
Could suffer several changes
Could have bugs
DO NOT USE IN PRODUCTION
Steps to follow
1. Add Data Binding library2. Apply binding to layout3. Create data binding object4. Do the binding
z
Adding Data Binding library
Project build.gradle
App build.gradle
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath "com.android.databinding:dataBinder:1.0-rc1"
}}
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'
z
Adding Data Binding library
Project build.gradle
App build.gradle
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath "com.android.databinding:dataBinder:1.0-rc1"
}}
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'
z
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'
Adding Data Binding library
Project build.gradle
App build.gradle
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath "com.android.databinding:dataBinder:1.0-rc1"
}}
z
Adding Data Binding library
Project build.gradle
App build.gradle
…and sync gradle!
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath "com.android.databinding:dataBinder:1.0-rc1"
}}
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'
z
Apply binding to layout
Before
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:text="@string/hello_world" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
z
Apply binding to layout
After
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="com.sergiandreplace.hellodatabinding.ViewData" />
</data>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{data.helloMessage}" />
</RelativeLayout>
</layout>
z
Apply binding to layout
New root tag <layout>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="com.sergiandreplace.hellodatabinding.ViewData" />
</data>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{data.helloMessage}" />
</RelativeLayout>
</layout>
z
Apply binding to layout
Two parts: data and layout itself
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="com.sergiandreplace.hellodatabinding.ViewData" />
</data>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{data.helloMessage}" />
</RelativeLayout>
</layout>
z
Apply binding to layout
Name of object to be injected and type of the object
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="com.sergiandreplace.hellodatabinding.ViewData" />
</data>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{data.helloMessage}" />
</RelativeLayout>
</layout>
z
Apply binding to layout
Binded property of the object
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="com.sergiandreplace.hellodatabinding.ViewData" />
</data>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{data.helloMessage}" />
</RelativeLayout>
</layout>
z
Create data binding object
This way:
Or this way:
public class ViewData {
public final String helloMessage;
public ViewData(String helloMessage) {
this.helloMessage = helloMessage;
}
}
public class ViewData {
private final String helloMessage;
public ViewData(String helloMessage) {
this.helloMessage = helloMessage;
}
public String getHelloMessage() {
return helloMessage;
}
}
z
Create data binding object
This way:
Or this way:
public class ViewData {
public final String helloMessage;
public ViewData(String helloMessage) {
this.helloMessage = helloMessage;
}
}
public class ViewData {
private final String helloMessage;
public ViewData(String helloMessage) {
this.helloMessage = helloMessage;
}
public String getHelloMessage() {
return helloMessage;
}
}
z
Create data binding object
This way:
Or this way:
public class ViewData {
public final String helloMessage;
public ViewData(String helloMessage) {
this.helloMessage = helloMessage;
}
}
public class ViewData {
private final String helloMessage;
public ViewData(String helloMessage) {
this.helloMessage = helloMessage;
}
public String getHelloMessage() {
return helloMessage;
}
}
z
Do the binding
On the activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
ViewData data = new ViewData(getString(R.string.hello_world));
binding.setData(data);
}
z
Do the binding
On the activity
We create the binding object
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
ViewData data = new ViewData(getString(R.string.hello_world));
binding.setData(data);
}
z
Do the binding
On the activity
Layout name Proper cased + Binding (activity_main.xml -> ActivityMainBinding)Generated on compilation. You must launch make before AS can recognizeOnly worked with canary (1.4 RC3)1.4 published yesterday (it should work)You must use Java 1.7 as language level
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
ViewData data = new ViewData(getString(R.string.hello_world));
binding.setData(data);
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
z
Do the binding
On the activity
Instantiate our ViewData object and set a message
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
ViewData data = new ViewData(getString(R.string.hello_world));
binding.setData(data);
}
z
Do the binding
On the activity
Give the data object to the binding for the painting
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
ViewData data = new ViewData(getString(R.string.hello_world));
binding.setData(data);
}
Execute…
…and it works!Pretty exciting, isn’t it?Not reallyLet’s see some other things we can do
z
Other things to do
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_offer”
android:visibility="@{product.isOffer? View.VISIBLE : View.GONE}"/>
<data>
<import type="android.view.View"/>
<variable name=“product” type=“com.sergiandreplace.hellodatabinding.product”/>
</data>
z
More things to do
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{StringUtils.getFormatCurrency(product.price,
product.currency)}”
/>
<data>
<import type="com.sergiandreplace.hellodatabinding.StringUtils"/>
<variable name=“product” type=“com.sergiandreplace.hellodatabinding.product”/>
</data>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{StringUtils.getFormatCurrency(product.Price) +
product.currency}”
/>
z
Include with Binding
<?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=“product" type=“com.sergiandreplace.hellodatabinding.Product"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/header"
bind:product="@{product}"/>
<include layout="@layout/detail"
bind:product="@{product}"/>
</LinearLayout>
</layout>
Merge not supported
Supported operators
Mathematical + - / * %
String concatenation +
Logical && ||
Binary & | ^
Unary + - ! ~
Shift >> >>> <<
Comparison == > < >= <=
Null ?? (a??b = a==null?b:a)
instanceof
Grouping ()
Literals - character, String,
numeric, null
Cast
Method calls
Field access
Array access []
Ternary operator ?:
z
Even list handling!
<data> <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/> <variable name="list" type="List<String>"/> <variable name="sparse" type="SparseArray<String>"/> <variable name="map" type="Map<String, String>"/> <variable name="index" type="int"/> <variable name="key" type="String"/></data>…android:text="@{list[index]}"…android:text="@{sparse[index]}"…android:text="@{map[key]}"
z
Observable objects
private static class Product extends BaseObservable { private String name; private String description; @Bindable public String getName() { return this.name; } @Bindable public String getDescription() { return this.description; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } public void setLastName(String description) { this. description = description; notifyPropertyChanged(BR. description); }}
z
Observable objects
Extends BaseObservable
private static class Product extends BaseObservable { private String name; private String description; @Bindable public String getName() { return this.name; } @Bindable public String getDescription() { return this.description; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } public void setLastName(String description) { this. description = description; notifyPropertyChanged(BR. description); }}
z
Observable objects
Declare getters as bindable
private static class Product extends BaseObservable { private String name; private String description; @Bindable public String getName() { return this.name; } @Bindable public String getDescription() { return this.description; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } public void setLastName(String description) { this. description = description; notifyPropertyChanged(BR. description); }}
z
Observable objects
Notify changesBR is like R for Bindables (aka: magic generated on compilation)
private static class Product extends BaseObservable { private String name; private String description; @Bindable public String getName() { return this.name; } @Bindable public String getDescription() { return this.description; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } public void setLastName(String description) { this. description = description; notifyPropertyChanged(BR.description); }}
z
Even easier: observable fields
private static class Product{
public final ObservableField<String> name =
new ObservableField<>();
public final ObservableField<String> description =
new ObservableField<>();
public final ObservableInt stock = new ObservableInt();
}
z
Even easier: observable fields
ObservableField just uses generics for any class
private static class Product{
public final ObservableField<String> name =
new ObservableField<>();
public final ObservableField<String> description =
new ObservableField<>();
public final ObservableInt stock = new ObservableInt();
}
z
Even easier: observable fields
ObservableInt, ObservableLong, ObservableParcelable, etc, already usable
private static class Product{
public final ObservableField<String> name =
new ObservableField<>();
public final ObservableField<String> description =
new ObservableField<>();
public final ObservableInt stock = new ObservableInt();
}
z
Even easier: observable fields
To use them…
private static class Product{
public final ObservableField<String> name =
new ObservableField<>();
public final ObservableField<String> description =
new ObservableField<>();
public final ObservableInt stock = new ObservableInt();
}
Product.stock.get();
product.name.set(“Biscuits”);
Attribute setters
Binding library tries to match the attribute setter with attribute name
Ex: on android:text=“@{…}” it looks for the setText method
In some cases we want something more accurated
We can create our own attribute setters
z
Attribute Setters
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Attribute setters
Not available for custom namespaces
Multiple parameters available
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl=“@{venue.imageUrl}”
app:error=“@{@drawable/venueError}”/>
There is even more
ConvertersArrays and lists handlingMessing up with listsViewStubs!Dynamic variables
But enough for today
We are all discovering it and learning what can be donePlay around with itCheck articles of really cool people on the Internet
For last…
Let’s talk a bit about architectures
• MVC – Model-View-Controller
• MVP – Model-View-Presenter
• MVVM – Model-View-ViewModel
Basic comparison
From Geeks with blog (geekswithblogs.net)
Final big advice
Do not put business logic in the View Model
CLEARLY separate business-logic and representation-logic
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/discount”
android:visibility="@{product.isOffer? 0.15 : 0}"/>
Final big advice
Do not put business logic in the View Model
CLEARLY separate business-logic and representation-logic
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/discount”
android:visibility="@{product.isOffer? 0.15 : 0}"/>
QA
uestions
nswers
20 yrs tinkering with computersA lot of time on software localizationOn Android since 2010 (Cupcake)Mobile R+D Lead at Worldine IberiaAndroid GDE (Google Developer Expert)I love making UIProud parent of a 8 years old OS fan
@sergiandreplace
sergiandreplace.comsergi.Martinez[at]gmail.com
About me – Sergi Martínez