Android Data Binding in action using MVVM pattern Fabio Collini
2 Ego slide @fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini codingjam.it
3 Agenda 1. Data Binding basics 2. Custom attributes 3. Components 4. Two Way Data Binding 5. Model View ViewModel
4 Example project
5 match_result.xml <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout style="@style/root_layout"
 xmlns:android=“http://schemas.android.com/apk/res/android"> 
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout>
6 public class TeamScore {
 private final String name;
 private final int goals; //constructor and getters
 } public class MatchResult {
 private final TeamScore homeTeam;
 private final TeamScore awayTeam;
 private final String gifUrl; //constructor and getters
 }
7 Butterknife Activity @Bind(R.id.result_gif) ImageView resultGif;
 @Bind(R.id.home_team) TextView homeTeam;
 @Bind(R.id.away_team) TextView awayTeam;
 @Bind(R.id.home_goals) TextView homeGoals;
 @Bind(R.id.away_goals) TextView awayGoals;
 
 @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.match_result);
 ButterKnife.bind(this);
 updateDetail(getIntent().getParcelableExtra("RESULT"));
 }
 private void updateDetail(MatchResult result) {
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(resultGif);
 if (result.getHomeTeam() != null) {
 homeTeam.setText(result.getHomeTeam().getName());
 homeGoals.setText(Integer.toString(result.getHomeTeam().getGoals()));
 }
 if (result.getAwayTeam() != null) {
 awayTeam.setText(result.getAwayTeam().getName());
 awayGoals.setText(Integer.toString(result.getAwayTeam().getGoals()));
 }
 }
DroidCon Italy - Torino - April 2016 - @fabioCollini 8 1Data Binding basics
9 Google I/O 2015
10 Is Data Binding still beta?
11 Is Data Binding still beta?
dataBinding {
 enabled = true
 }
 12 build.gradle android {
 compileSdkVersion 23
 buildToolsVersion "23.0.2"
 
 defaultConfig {
 //...
 ____}
 buildTypes {
 //...
 ____} }
<?xml version="1.0" encoding="utf-8"?>
 <layout>
 <LinearLayout style=“@style/root_layout" xmlns:android="http://schemas.android.com/apk/res/android">
 
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout>
 </layout> 13 Data Binding layout <LinearLayout style="@style/root_layout"
 xmlns:android=“http://schemas.android.com/apk/res/android"> 
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout> <?xml version="1.0" encoding="utf-8"?> <layout> </layout>
<LinearLayout style="@style/root_layout"
 xmlns:android=“http://schemas.android.com/apk/res/android"> 
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout> 14 One layout traversal match_result.xmlMatchResultBinding.java Auto generated class <?xml version="1.0" encoding="utf-8"?> <layout> </layout> public class MatchResultBinding extends android.databinding.ViewDataBinding {
 
 // ...
 public final android.widget.ImageView resultGif;
 public final android.widget.TextView homeTeam;
 public final android.widget.TextView homeGoals;
 public final android.widget.TextView awayTeam;
 public final android.widget.TextView awayGoals;
 // ...
 }
15 Activity 
 @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.match_result); updateDetail(getIntent().getParcelableExtra("RESULT"));
 }____ 
 private void updateDetail(MatchResult result) {
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 if (result.getHomeTeam() != null) {
 binding.homeTeam.setText(result.getHomeTeam().getName());
 binding.homeGoals.setText(Integer.toString(result.getHomeTeam().getGoals()));
 }___
 if (result.getAwayTeam() != null) {
 binding.awayTeam.setText(result.getAwayTeam().getName());
 binding.awayGoals.setText(Integer.toString(result.getAwayTeam().getGoals()));
 }__
 }_ private MatchResultBinding binding;__
16 Variable in layout <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"> Automatic null check <data>
 <variable name="result" type="it.droidcon.databinding.MatchResult"/>
 </data> <LinearLayout style="@style/root_layout">
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView style=“@style/name"
 android:text="@{result.homeTeam.name}"/>
 <TextView style="@style/goals"
 android:text="@{Integer.toString(result.homeTeam.goals)}"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView style="@style/name"
 android:text="@{result.awayTeam.name}"/>
 <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/>
 </LinearLayout>
 </LinearLayout>
 </layout>
17 Activity private MatchResultBinding binding; 
 @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.match_result); updateDetail(getIntent().getParcelableExtra("RESULT"));
 }____ 
 private void updateDetail(MatchResult result) {
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 binding.setResult(result); }_
18 Code in XML? Are you serious?!?
19 Complex code in XML is NOT a best practice
DroidCon Italy - Torino - April 2016 - @fabioCollini 20 2Custom attributes
21 @BindingAdapter <ImageView android:id="@+id/result_gif" style="@style/gif"/> private void updateDetail(MatchResult result) {
 binding.setResult(result);
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 } <ImageView style=“@style/gif" app:imageUrl="@{result.gifUrl}"/> @BindingAdapter("imageUrl")
 public static void loadImage(ImageView view, String url) {
 Glide.with(view.getContext()).load(url)
 .placeholder(R.drawable.loading).into(view);
 }
22 public class MatchResultActivity extends AppCompatActivity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 MatchResultBinding binding =
 DataBindingUtil.setContentView(this, R.layout.match_result);
 MatchResult result = getIntent().getParcelableExtra("RESULT");
 binding.setResult(result);
 }
 }
23 Multiple parameters @BindingAdapter({"imageUrl", "placeholder"})
 public static void loadImage(ImageView view, String url, Drawable placeholder) {
 Glide.with(view.getContext()).load(url)
 .placeholder(placeholder).into(view);
 }
 <ImageView style="@style/gif"
 app:imageUrl="@{result.gifUrl}" app:placeholder="@{@drawable/loading}"/>
Annotated methods are static but… @BindingAdapter("something")
 public static void bindSomething(View view, AnyObject b) {
 MyBinding binding = DataBindingUtil.findBinding(view); 
 MyObject myObject = binding.getMyObject();
 //… TextView myTextView = 
 binding.myTextView; //… } Can be any object Get the layout binding Get the connected objects Access to all the views Can be defined anywhere Can be used everywhere Can be any View
25 BindingConversion @BindingConversion
 public static @ColorRes int convertEnumToColor(MyEnum value) {
 switch (value) {
 case VALUE1:
 return R.color.color1;
 case VALUE2:
 return R.color.color2;
 case VALUE3:
 return R.color.color3;
 default:
 return R.color.color4;
 }
 } <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textColor="@{myObject.myEnum}"/>
@BindingConversion
 public static String convertScoreToString(TeamScore score) {
 return Integer.toString(score.getGoals());
 }___
 <TextView style="@style/goals” android:text="@{result.awayTeam}"/>
 26 BindingConversion & BindingAdapter <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/>
<TextView style="@style/goals" app:goals="@{result.awayTeam.goals}"/> @BindingAdapter("goals")
 public static void bindGoals(TextView view, int goals) {
 view.setText(Integer.toString(goals));
 }__
 @BindingConversion
 public static String convertScoreToString(TeamScore score) {
 return Integer.toString(score.getGoals());
 }___
 27 BindingConversion & BindingAdapter <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/> <TextView style="@style/goals” android:text="@{result.awayTeam}"/>

<TextView style="@style/goals" app:goals="@{result.awayTeam.goals}"/> @BindingAdapter("goals")
 public static void bindGoals(TextView view, int goals) {
 view.setText(Integer.toString(goals));
 }__
 @BindingConversion
 public static String convertScoreToString(TeamScore score) {
 return Integer.toString(score.getGoals());
 }___
 <TextView style="@style/goals” android:text="@{result.awayTeam}"/>
 28 BindingConversion & BindingAdapter <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/>
29 FontBinding
DroidCon Italy - Torino - April 2016 - @fabioCollini 30 3Components
31 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 <data>
 <variable name="result"
 type="it.droidcon.databinding.MatchResult"/>
 </data>
 <LinearLayout style="@style/root_layout">
 <ImageView style="@style/gif" app:imageUrl="@{result.gifUrl}"/>
 
 
 <LinearLayout style="@style/team_layout">
 <TextView style="@style/name"
 android:text="@{result.awayTeam.name}"/>
 <TextView style="@style/goals"
 app:goals="@{result.awayTeam.goals}"/>
 </LinearLayout>
 </LinearLayout>
 </layout> <LinearLayout style="@style/team_layout">
 <TextView style="@style/name"
 android:text="@{result.homeTeam.name}"/>
 <TextView style="@style/goals"
 app:goals="@{result.homeTeam.goals}"/>
 </LinearLayout>
32 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 </layout> team_detail.xml <data>
 <variable name="team"
 type="it.droidcon.databinding.TeamScore"/>
 </data>
 <LinearLayout style="@style/team_layout">
 <TextView style="@style/name"
 android:text="@{team.name}"/>
 <TextView style="@style/goals"
 app:goals="@{team.goals}"/>
 </LinearLayout>
</data>
 <LinearLayout style="@style/root_layout">
 <ImageView style="@style/gif" app:imageUrl="@{result.gifUrl}"/>
 
 
 <include layout="@layout/team_detail"/> <include layout="@layout/team_detail"/> </LinearLayout>
 </layout> <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 <data>
 <variable name="result"
 type=“it.droidcon.databinding.MatchResult"/> 

</data>
 <LinearLayout style="@style/root_layout">
 <ImageView style="@style/gif" app:imageUrl="@{result.gifUrl}"/>
 
 
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools">
 <data>
 <variable name="result"
 type="it.droidcon.databinding.MatchResult"/>
 <include layout="@layout/team_detail"
 /> <include layout="@layout/team_detail"
 /> </LinearLayout>
 </layout> bind:team="@{result.homeTeam}" bind:team="@{result.awayTeam}"
</data>
 <LinearLayout style="@style/root_layout" >
 <ImageView style="@style/gif" app:imageUrl="@{result.gifUrl}"/>
 
 
 android:background="@{backgroundColor ?? @color/color1}" <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools">
 <data>
 <variable name="result"
 type="it.droidcon.databinding.MatchResult"/>
 <include layout="@layout/team_detail"
 /> <include layout="@layout/team_detail"
 /> </LinearLayout>
 </layout> bind:team="@{result.homeTeam}" bind:team="@{result.awayTeam}" <variable name="backgroundColor" type="Integer" />
DroidCon Italy - Torino - April 2016 - @fabioCollini 36 4Two Way Data Binding
37
38 public class ContactInfo {
 public String message;
 
 public boolean messageAvailable;
 }__
39 public class ContactInfo {
 public String message;
 
 public boolean messageAvailable;
 }__ <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="info"
 type="it.droidcon.databinding.ContactInfo" />
 </data>
 
 <LinearLayout style="@style/contact_root">
 <EditText
 style="@style/contact_text"
 android:enabled="@{info.messageAvailable}"
 android:text="@{info.message}" />
 
 <Button
 style="@style/contact_button"
 android:enabled="@{info.messageAvailable}"
 android:onClick="send" />
 </LinearLayout>
 </layout>
40 public class ContactInfo {
 public String message;
 
 public boolean messageAvailable;
 }__ public class ContactActivity extends AppCompatActivity {
 private ContactInfo contactInfo;
 private ContactBinding binding;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.contact);
 contactInfo = new ContactInfo();
 binding.setInfo(contactInfo);
 
 binding.getRoot().postDelayed(() -> {
 contactInfo.message = "my message";
 contactInfo.messageAvailable = true;
 }, 2000);
 }
 
 public void send(View view) {
 Snackbar.make(binding.getRoot(), contactInfo.message, LENGTH_LONG).show();
 }
 }
41
42 Views are not automatically updated :( package android.databinding;
 
 public interface Observable {
 
 void addOnPropertyChangedCallback( OnPropertyChangedCallback callback);
 
 void removeOnPropertyChangedCallback( OnPropertyChangedCallback callback);
 
 abstract class OnPropertyChangedCallback {
 public abstract void onPropertyChanged( Observable sender, int propertyId);
 }
 }
43 Observable hierarchy
public String getMessage() {
 return message;
 }____
 
 public boolean isMessageAvailable() {
 return messageAvailable;
 }___
 
 public void setMessage(String message) {
 this.message = message;
 
 }__
 
 public void setMessageAvailable(boolean messageAvailable) {
 this.messageAvailable = messageAvailable;
 
 }_
 public class ContactInfo
 private String message;
 
 private boolean messageAvailable;
 44 extends BaseObservable { } notifyPropertyChanged(BR.message); notifyPropertyChanged(BR.messageAvailable); @Bindable @Bindable
45 public class ContactInfo {
 public final ObservableField<String> message = new ObservableField<>();_
 
 public final ObservableBoolean messageAvailable = new ObservableBoolean();_
 }__
46 public class ContactInfo {
 public final ObservableField<String> message = new ObservableField<>();_
 
 public final ObservableBoolean messageAvailable = new ObservableBoolean();_
 }__ public class ContactActivity extends AppCompatActivity {
 private ContactInfo contactInfo;
 private ContactBinding binding;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.contact);
 contactInfo = new ContactInfo();
 binding.setInfo(contactInfo);
 
 binding.getRoot().postDelayed(() -> {
 contactInfo.message.set("my message");
 contactInfo.messageAvailable.set(true);
 }, 2000); }___
 
 public void send(View view) {
 Snackbar.make(binding.getRoot(), contactInfo.message.get(), LENGTH_LONG).show();
 }__
 }_
<layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="info"
 type="it.droidcon.databinding.ContactInfo" />
 </data>
 
 <LinearLayout style="@style/contact_root">
 <EditText
 style="@style/contact_text"
 android:enabled="@{info.messageAvailable}"
 android:text="@{info.message}" />
 
 <Button
 style="@style/contact_button"
 android:enabled="@{info.messageAvailable}"
 android:onClick="send" />
 </LinearLayout>
 </layout> 47 public class ContactInfo {
 public final ObservableField<String> message = new ObservableField<>();_
 
 public final ObservableBoolean messageAvailable = new ObservableBoolean();_
 }__ ObservableField<String> ObservableBoolean ObservableBoolean
48
49 Two way Data Binding @BindingAdapter("binding")
 public static void bindEditText(EditText view, final ObservableString observable) {
 Pair<ObservableString, TextWatcherAdapter> pair = (Pair) view.getTag(R.id.bound_observable);
 if (pair == null || pair.first != observable) {
 if (pair != null)
 view.removeTextChangedListener(pair.second);
 TextWatcherAdapter watcher = new TextWatcherAdapter() {
 @Override public void onTextChanged(CharSequence s, int a, int b, int c) {
 observable.set(s.toString());
 }
 };
 view.setTag(R.id.bound_observable, new Pair<>(observable, watcher));
 view.addTextChangedListener(watcher);
 }
 String newValue = observable.get();
 if (!view.getText().toString().equals(newValue))
 view.setText(newValue);
 }
 medium.com/@fabioCollini/android-data-binding-f9f9d3afc761
50
51 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="info"
 type="it.droidcon.databinding.ContactInfo" />
 </data>
 
 <LinearLayout style="@style/contact_root">
 <EditText
 style="@style/contact_text"
 android:enabled="@{info.messageAvailable}"
 android:text="@={info.message}" />
 
 <Button
 style="@style/contact_button"
 android:enabled="@{info.messageAvailable}"
 android:onClick="send" />
 </LinearLayout>
 </layout> Two way data binding
52
53
54 Layout ContactInfo Binding TextWatcherset(…) addOnProperty ChangedCallbackset(…) if (changed) WeakReference if (changed)
DroidCon Italy - Torino - April 2016 - @fabioCollini 55 5MVVM
56 MatchResultViewModel public class MatchResultViewModel { 
 public final ObservableField<MatchResult> result = new ObservableField<>();
 
 public final ObservableBoolean loading = new ObservableBoolean();
 
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }
 }
<layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools">
 <data>
 <variable name="viewModel"
 type="it.droidcon.databinding.MatchResultViewModel"/>
 </data>
 <LinearLayout style="@style/root_layout">
 <ImageView style="@style/gif" app:imageUrl="@{viewModel.result.gifUrl}"/>
 
 
 <include layout="@layout/team_detail"
 /> <include layout="@layout/team_detail"
 /> </LinearLayout>
 </layout> bind:team="@{viewModel.result.homeTeam}" bind:team="@{viewModel.result.awayTeam}" ObservableField
58 Visibility <FrameLayout style="@style/progress_layout"
 android:visibility= "@{viewModel.loading ? View.VISIBLE : View.GONE}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:bind="http://schemas.android.com/tools">
 <data> <import type="android.view.View"/>
 <variable name="viewModel"
 type="it.droidcon.databinding.MatchResultViewModel"/> </data>
 <FrameLayout style="@style/main_container"> 
 <LinearLayout style="@style/root_layout">
 <!-- ... -->
 </LinearLayout>
 
 
 </FrameLayout>
 </layout>
59 Visibility <FrameLayout style="@style/progress_layout"
 android:visibility= "@{viewModel.loading ? View.VISIBLE : View.GONE}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> @BindingConversion
 public static int convertBooleanToVisibility(boolean b) {
 return b ? View.VISIBLE : View.GONE;
 }___

60 Visibility <FrameLayout style="@style/progress_layout"
 android:visibility="@{viewModel.loading}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> @BindingConversion
 public static int convertBooleanToVisibility(boolean b) {
 return b ? View.VISIBLE : View.GONE;
 }___
 @BindingAdapter("visibleOrGone")
 public static void bindVisibleOrGone(View view, boolean b) {
 view.setVisibility(b ? View.VISIBLE : View.GONE);
 }____
61 Visibility <FrameLayout style="@style/progress_layout"
 app:visibleOrGone="@{viewModel.loading}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> @BindingConversion
 public static int convertBooleanToVisibility(boolean b) {
 return b ? View.VISIBLE : View.GONE;
 }___
 @BindingAdapter("visibleOrGone")
 public static void bindVisibleOrGone(View view, boolean b) {
 view.setVisibility(b ? View.VISIBLE : View.GONE);
 }____ @BindingAdapter("visible")
 public static void bindVisible(View view, boolean b) {
 view.setVisibility(b ? View.VISIBLE : View.INVISIBLE);
 }
<LinearLayout style="@style/root_layout"
 android:onClick="@{???}">
 <!-- ... -->
 </LinearLayout>
 62 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
public final_View.OnClickListener reloadClickListener = new View.OnClickListener() {
 @Override public void onClick(View v) {
 reload();
 }_
 };
 <LinearLayout style="@style/root_layout"
 android:onClick="@{viewModel.reloadClickListener}">
 <!-- ... -->
 </LinearLayout>
 63 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
<LinearLayout style="@style/root_layout"
 android:onClick="@{viewModel::reload}">
 <!-- ... -->
 </LinearLayout>
 64 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload(View v) { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
<LinearLayout style="@style/root_layout"
 android:onClick="@{v -> viewModel.reload()}">
 <!-- ... -->
 </LinearLayout>
 65 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__ 66 }___ @BindingAdapter("android:onClick")
 public static void bindOnClick(View view, final Runnable listener) {
 view.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) {
 listener.run();
 }____
 });
 }___ <LinearLayout style="@style/root_layout"
 android:onClick="@{v -> viewModel.reload()}">
 <!-- ... -->
 </LinearLayout>

public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__ <LinearLayout style="@style/root_layout"
 android:onClick="@{viewModel::reload}">
 <!-- ... -->
 </LinearLayout>
 67 }___ @BindingAdapter("android:onClick")
 public static void bindOnClick(View view, final Runnable listener) {
 view.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) {
 listener.run();
 }____
 });
 }___
68 Final layout <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:bind="http://schemas.android.com/tools">
 <data>
 <!-- ... -->
 </data>
 <FrameLayout style="@style/main_container"> 
 <LinearLayout style="@style/root_layout"
 android:onClick=“@{viewModel::reload}”>
 <!-- ... -->
 </LinearLayout>
 
 <FrameLayout style="@style/progress_layout"
 app:visibleOrGone="@{viewModel.loading}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout>
 
 </FrameLayout>
 </layout>
69 public interface Reloadable {
 String getErrorMessage();
 
 void reload();
 }
 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="reloadable"
 type="it.droidcon.databinding.Reloadable"/>
 </data>
 
 <LinearLayout style="@style/reload_layout">
 <TextView
 style="@style/error_message"
 android:text="@{reloadable.errorMessage}"/>
 
 <Button
 style="@style/reload"
 android:onClick="@{reloadable::reload}"
 android:text="@string/reload"/>
 </LinearLayout>
 </layout>
70 Model View ViewModel View ViewModel Model DataBinding Retained on configuration change Saved in Activity or Fragment state Activity or Fragment
71 github.com/fabioCollini/mv2m Android MVVM lightweight library based on Android Data Binding
View ViewModel RetrofitService reload update binding Model View ViewModel RetrofitServiceModel request response binding
Testable code Data binding and MVVM
MockService MockService RetrofitService RetrofitServiceViewModel reload update Model request response JVM Test ViewModel ModelJVM Test assert when().thenReturn() verify
MockServiceRetrofitService MockServiceRetrofitService View ViewModel perform(click()) update binding Model request response EspressoTest View ViewModel ModelEspressoTest onView verify when().thenReturn() reload binding
76 Data binding You can write all your business logic in an huge xml file ———————————————————————— ————————————
Custom attributes Reusable UI code 77 Data binding You can write all your business logic in an huge xml file ———————————————————————— ———————————— Includes UI components MVVM Testable code
78 Thanks for your attention! androidavanzato.it Questions?
79 Links developer.android.com/tools/data-binding/guide.html Is Databinding still beta? - Google plus medium.com/@fabioCollini/android-data-binding-f9f9d3afc761 github.com/fabioCollini/mv2m github.com/commit-non-javisti/CoseNonJavisteAndroidApp halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android

Data Binding in Action using MVVM pattern

  • 1.
    Android Data Bindingin action using MVVM pattern Fabio Collini
  • 2.
  • 3.
    3 Agenda 1. Data Bindingbasics 2. Custom attributes 3. Components 4. Two Way Data Binding 5. Model View ViewModel
  • 4.
  • 5.
    5 match_result.xml <?xml version="1.0" encoding="utf-8"?>
 <LinearLayoutstyle="@style/root_layout"
 xmlns:android=“http://schemas.android.com/apk/res/android"> 
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout>
  • 6.
    6 public class TeamScore{
 private final String name;
 private final int goals; //constructor and getters
 } public class MatchResult {
 private final TeamScore homeTeam;
 private final TeamScore awayTeam;
 private final String gifUrl; //constructor and getters
 }
  • 7.
    7 Butterknife Activity @Bind(R.id.result_gif) ImageViewresultGif;
 @Bind(R.id.home_team) TextView homeTeam;
 @Bind(R.id.away_team) TextView awayTeam;
 @Bind(R.id.home_goals) TextView homeGoals;
 @Bind(R.id.away_goals) TextView awayGoals;
 
 @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.match_result);
 ButterKnife.bind(this);
 updateDetail(getIntent().getParcelableExtra("RESULT"));
 }
 private void updateDetail(MatchResult result) {
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(resultGif);
 if (result.getHomeTeam() != null) {
 homeTeam.setText(result.getHomeTeam().getName());
 homeGoals.setText(Integer.toString(result.getHomeTeam().getGoals()));
 }
 if (result.getAwayTeam() != null) {
 awayTeam.setText(result.getAwayTeam().getName());
 awayGoals.setText(Integer.toString(result.getAwayTeam().getGoals()));
 }
 }
  • 8.
    DroidCon Italy -Torino - April 2016 - @fabioCollini 8 1Data Binding basics
  • 9.
  • 10.
    10 Is Data Bindingstill beta?
  • 11.
    11 Is Data Bindingstill beta?
  • 12.
    dataBinding {
 enabled =true
 }
 12 build.gradle android {
 compileSdkVersion 23
 buildToolsVersion "23.0.2"
 
 defaultConfig {
 //...
 ____}
 buildTypes {
 //...
 ____} }
  • 13.
    <?xml version="1.0" encoding="utf-8"?>
 <layout>
 <LinearLayoutstyle=“@style/root_layout" xmlns:android="http://schemas.android.com/apk/res/android">
 
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout>
 </layout> 13 Data Binding layout <LinearLayout style="@style/root_layout"
 xmlns:android=“http://schemas.android.com/apk/res/android"> 
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout> <?xml version="1.0" encoding="utf-8"?> <layout> </layout>
  • 14.
    <LinearLayout style="@style/root_layout"
 xmlns:android=“http://schemas.android.com/apk/res/android"> 
 <ImageView android:id="@+id/result_gif"style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/home_team" style="@style/name"/>
 <TextView android:id="@+id/home_goals" style="@style/goals"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView android:id="@+id/away_team" style="@style/name"/>
 <TextView android:id="@+id/away_goals" style="@style/goals"/>
 </LinearLayout>
 </LinearLayout> 14 One layout traversal match_result.xmlMatchResultBinding.java Auto generated class <?xml version="1.0" encoding="utf-8"?> <layout> </layout> public class MatchResultBinding extends android.databinding.ViewDataBinding {
 
 // ...
 public final android.widget.ImageView resultGif;
 public final android.widget.TextView homeTeam;
 public final android.widget.TextView homeGoals;
 public final android.widget.TextView awayTeam;
 public final android.widget.TextView awayGoals;
 // ...
 }
  • 15.
    15 Activity 
 @Override protected voidonCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.match_result); updateDetail(getIntent().getParcelableExtra("RESULT"));
 }____ 
 private void updateDetail(MatchResult result) {
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 if (result.getHomeTeam() != null) {
 binding.homeTeam.setText(result.getHomeTeam().getName());
 binding.homeGoals.setText(Integer.toString(result.getHomeTeam().getGoals()));
 }___
 if (result.getAwayTeam() != null) {
 binding.awayTeam.setText(result.getAwayTeam().getName());
 binding.awayGoals.setText(Integer.toString(result.getAwayTeam().getGoals()));
 }__
 }_ private MatchResultBinding binding;__
  • 16.
    16 Variable in layout <?xmlversion="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"> Automatic null check <data>
 <variable name="result" type="it.droidcon.databinding.MatchResult"/>
 </data> <LinearLayout style="@style/root_layout">
 <ImageView android:id="@+id/result_gif" style="@style/gif"/>
 
 <LinearLayout style="@style/team_layout">
 <TextView style=“@style/name"
 android:text="@{result.homeTeam.name}"/>
 <TextView style="@style/goals"
 android:text="@{Integer.toString(result.homeTeam.goals)}"/>
 </LinearLayout>
 
 <LinearLayout style="@style/team_layout">
 <TextView style="@style/name"
 android:text="@{result.awayTeam.name}"/>
 <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/>
 </LinearLayout>
 </LinearLayout>
 </layout>
  • 17.
    17 Activity private MatchResultBinding binding; 
 @Overrideprotected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.match_result); updateDetail(getIntent().getParcelableExtra("RESULT"));
 }____ 
 private void updateDetail(MatchResult result) {
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 binding.setResult(result); }_
  • 18.
    18 Code in XML? Areyou serious?!?
  • 19.
    19 Complex code inXML is NOT a best practice
  • 20.
    DroidCon Italy -Torino - April 2016 - @fabioCollini 20 2Custom attributes
  • 21.
    21 @BindingAdapter <ImageView android:id="@+id/result_gif" style="@style/gif"/> privatevoid updateDetail(MatchResult result) {
 binding.setResult(result);
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 } <ImageView style=“@style/gif" app:imageUrl="@{result.gifUrl}"/> @BindingAdapter("imageUrl")
 public static void loadImage(ImageView view, String url) {
 Glide.with(view.getContext()).load(url)
 .placeholder(R.drawable.loading).into(view);
 }
  • 22.
    22 public class MatchResultActivityextends AppCompatActivity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 MatchResultBinding binding =
 DataBindingUtil.setContentView(this, R.layout.match_result);
 MatchResult result = getIntent().getParcelableExtra("RESULT");
 binding.setResult(result);
 }
 }
  • 23.
    23 Multiple parameters @BindingAdapter({"imageUrl", "placeholder"})
 publicstatic void loadImage(ImageView view, String url, Drawable placeholder) {
 Glide.with(view.getContext()).load(url)
 .placeholder(placeholder).into(view);
 }
 <ImageView style="@style/gif"
 app:imageUrl="@{result.gifUrl}" app:placeholder="@{@drawable/loading}"/>
  • 24.
    Annotated methods arestatic but… @BindingAdapter("something")
 public static void bindSomething(View view, AnyObject b) {
 MyBinding binding = DataBindingUtil.findBinding(view); 
 MyObject myObject = binding.getMyObject();
 //… TextView myTextView = 
 binding.myTextView; //… } Can be any object Get the layout binding Get the connected objects Access to all the views Can be defined anywhere Can be used everywhere Can be any View
  • 25.
    25 BindingConversion @BindingConversion
 public static @ColorResint convertEnumToColor(MyEnum value) {
 switch (value) {
 case VALUE1:
 return R.color.color1;
 case VALUE2:
 return R.color.color2;
 case VALUE3:
 return R.color.color3;
 default:
 return R.color.color4;
 }
 } <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textColor="@{myObject.myEnum}"/>
  • 26.
    @BindingConversion
 public static StringconvertScoreToString(TeamScore score) {
 return Integer.toString(score.getGoals());
 }___
 <TextView style="@style/goals” android:text="@{result.awayTeam}"/>
 26 BindingConversion & BindingAdapter <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/>
  • 27.
    <TextView style="@style/goals" app:goals="@{result.awayTeam.goals}"/> @BindingAdapter("goals")
 public staticvoid bindGoals(TextView view, int goals) {
 view.setText(Integer.toString(goals));
 }__
 @BindingConversion
 public static String convertScoreToString(TeamScore score) {
 return Integer.toString(score.getGoals());
 }___
 27 BindingConversion & BindingAdapter <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/> <TextView style="@style/goals” android:text="@{result.awayTeam}"/>

  • 28.
    <TextView style="@style/goals" app:goals="@{result.awayTeam.goals}"/> @BindingAdapter("goals")
 public staticvoid bindGoals(TextView view, int goals) {
 view.setText(Integer.toString(goals));
 }__
 @BindingConversion
 public static String convertScoreToString(TeamScore score) {
 return Integer.toString(score.getGoals());
 }___
 <TextView style="@style/goals” android:text="@{result.awayTeam}"/>
 28 BindingConversion & BindingAdapter <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/>
  • 29.
  • 30.
    DroidCon Italy -Torino - April 2016 - @fabioCollini 30 3Components
  • 31.
    31 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 <data>
 <variable name="result"
 type="it.droidcon.databinding.MatchResult"/>
 </data>
 <LinearLayoutstyle="@style/root_layout">
 <ImageView style="@style/gif" app:imageUrl="@{result.gifUrl}"/>
 
 
 <LinearLayout style="@style/team_layout">
 <TextView style="@style/name"
 android:text="@{result.awayTeam.name}"/>
 <TextView style="@style/goals"
 app:goals="@{result.awayTeam.goals}"/>
 </LinearLayout>
 </LinearLayout>
 </layout> <LinearLayout style="@style/team_layout">
 <TextView style="@style/name"
 android:text="@{result.homeTeam.name}"/>
 <TextView style="@style/goals"
 app:goals="@{result.homeTeam.goals}"/>
 </LinearLayout>
  • 32.
  • 33.
    </data>
 <LinearLayout style="@style/root_layout">
 <ImageView style="@style/gif"app:imageUrl="@{result.gifUrl}"/>
 
 
 <include layout="@layout/team_detail"/> <include layout="@layout/team_detail"/> </LinearLayout>
 </layout> <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 <data>
 <variable name="result"
 type=“it.droidcon.databinding.MatchResult"/> 

  • 34.
    </data>
 <LinearLayout style="@style/root_layout">
 <ImageView style="@style/gif"app:imageUrl="@{result.gifUrl}"/>
 
 
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools">
 <data>
 <variable name="result"
 type="it.droidcon.databinding.MatchResult"/>
 <include layout="@layout/team_detail"
 /> <include layout="@layout/team_detail"
 /> </LinearLayout>
 </layout> bind:team="@{result.homeTeam}" bind:team="@{result.awayTeam}"
  • 35.
    </data>
 <LinearLayout style="@style/root_layout" >
 <ImageView style="@style/gif"app:imageUrl="@{result.gifUrl}"/>
 
 
 android:background="@{backgroundColor ?? @color/color1}" <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools">
 <data>
 <variable name="result"
 type="it.droidcon.databinding.MatchResult"/>
 <include layout="@layout/team_detail"
 /> <include layout="@layout/team_detail"
 /> </LinearLayout>
 </layout> bind:team="@{result.homeTeam}" bind:team="@{result.awayTeam}" <variable name="backgroundColor" type="Integer" />
  • 36.
    DroidCon Italy -Torino - April 2016 - @fabioCollini 36 4Two Way Data Binding
  • 37.
  • 38.
    38 public class ContactInfo{
 public String message;
 
 public boolean messageAvailable;
 }__
  • 39.
    39 public class ContactInfo{
 public String message;
 
 public boolean messageAvailable;
 }__ <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="info"
 type="it.droidcon.databinding.ContactInfo" />
 </data>
 
 <LinearLayout style="@style/contact_root">
 <EditText
 style="@style/contact_text"
 android:enabled="@{info.messageAvailable}"
 android:text="@{info.message}" />
 
 <Button
 style="@style/contact_button"
 android:enabled="@{info.messageAvailable}"
 android:onClick="send" />
 </LinearLayout>
 </layout>
  • 40.
    40 public class ContactInfo{
 public String message;
 
 public boolean messageAvailable;
 }__ public class ContactActivity extends AppCompatActivity {
 private ContactInfo contactInfo;
 private ContactBinding binding;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.contact);
 contactInfo = new ContactInfo();
 binding.setInfo(contactInfo);
 
 binding.getRoot().postDelayed(() -> {
 contactInfo.message = "my message";
 contactInfo.messageAvailable = true;
 }, 2000);
 }
 
 public void send(View view) {
 Snackbar.make(binding.getRoot(), contactInfo.message, LENGTH_LONG).show();
 }
 }
  • 41.
  • 42.
    42 Views are notautomatically updated :( package android.databinding;
 
 public interface Observable {
 
 void addOnPropertyChangedCallback( OnPropertyChangedCallback callback);
 
 void removeOnPropertyChangedCallback( OnPropertyChangedCallback callback);
 
 abstract class OnPropertyChangedCallback {
 public abstract void onPropertyChanged( Observable sender, int propertyId);
 }
 }
  • 43.
  • 44.
    public String getMessage(){
 return message;
 }____
 
 public boolean isMessageAvailable() {
 return messageAvailable;
 }___
 
 public void setMessage(String message) {
 this.message = message;
 
 }__
 
 public void setMessageAvailable(boolean messageAvailable) {
 this.messageAvailable = messageAvailable;
 
 }_
 public class ContactInfo
 private String message;
 
 private boolean messageAvailable;
 44 extends BaseObservable { } notifyPropertyChanged(BR.message); notifyPropertyChanged(BR.messageAvailable); @Bindable @Bindable
  • 45.
    45 public class ContactInfo{
 public final ObservableField<String> message = new ObservableField<>();_
 
 public final ObservableBoolean messageAvailable = new ObservableBoolean();_
 }__
  • 46.
    46 public class ContactInfo{
 public final ObservableField<String> message = new ObservableField<>();_
 
 public final ObservableBoolean messageAvailable = new ObservableBoolean();_
 }__ public class ContactActivity extends AppCompatActivity {
 private ContactInfo contactInfo;
 private ContactBinding binding;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 binding = DataBindingUtil.setContentView(this, R.layout.contact);
 contactInfo = new ContactInfo();
 binding.setInfo(contactInfo);
 
 binding.getRoot().postDelayed(() -> {
 contactInfo.message.set("my message");
 contactInfo.messageAvailable.set(true);
 }, 2000); }___
 
 public void send(View view) {
 Snackbar.make(binding.getRoot(), contactInfo.message.get(), LENGTH_LONG).show();
 }__
 }_
  • 47.
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="info"
 type="it.droidcon.databinding.ContactInfo"/>
 </data>
 
 <LinearLayout style="@style/contact_root">
 <EditText
 style="@style/contact_text"
 android:enabled="@{info.messageAvailable}"
 android:text="@{info.message}" />
 
 <Button
 style="@style/contact_button"
 android:enabled="@{info.messageAvailable}"
 android:onClick="send" />
 </LinearLayout>
 </layout> 47 public class ContactInfo {
 public final ObservableField<String> message = new ObservableField<>();_
 
 public final ObservableBoolean messageAvailable = new ObservableBoolean();_
 }__ ObservableField<String> ObservableBoolean ObservableBoolean
  • 48.
  • 49.
    49 Two way DataBinding @BindingAdapter("binding")
 public static void bindEditText(EditText view, final ObservableString observable) {
 Pair<ObservableString, TextWatcherAdapter> pair = (Pair) view.getTag(R.id.bound_observable);
 if (pair == null || pair.first != observable) {
 if (pair != null)
 view.removeTextChangedListener(pair.second);
 TextWatcherAdapter watcher = new TextWatcherAdapter() {
 @Override public void onTextChanged(CharSequence s, int a, int b, int c) {
 observable.set(s.toString());
 }
 };
 view.setTag(R.id.bound_observable, new Pair<>(observable, watcher));
 view.addTextChangedListener(watcher);
 }
 String newValue = observable.get();
 if (!view.getText().toString().equals(newValue))
 view.setText(newValue);
 }
 medium.com/@fabioCollini/android-data-binding-f9f9d3afc761
  • 50.
  • 51.
    51 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="info"
 type="it.droidcon.databinding.ContactInfo"/>
 </data>
 
 <LinearLayout style="@style/contact_root">
 <EditText
 style="@style/contact_text"
 android:enabled="@{info.messageAvailable}"
 android:text="@={info.message}" />
 
 <Button
 style="@style/contact_button"
 android:enabled="@{info.messageAvailable}"
 android:onClick="send" />
 </LinearLayout>
 </layout> Two way data binding
  • 52.
  • 53.
  • 54.
  • 55.
    DroidCon Italy -Torino - April 2016 - @fabioCollini 55 5MVVM
  • 56.
    56 MatchResultViewModel public class MatchResultViewModel{ 
 public final ObservableField<MatchResult> result = new ObservableField<>();
 
 public final ObservableBoolean loading = new ObservableBoolean();
 
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }
 }
  • 57.
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools">
 <data>
 <variable name="viewModel"
 type="it.droidcon.databinding.MatchResultViewModel"/>
 </data>
 <LinearLayoutstyle="@style/root_layout">
 <ImageView style="@style/gif" app:imageUrl="@{viewModel.result.gifUrl}"/>
 
 
 <include layout="@layout/team_detail"
 /> <include layout="@layout/team_detail"
 /> </LinearLayout>
 </layout> bind:team="@{viewModel.result.homeTeam}" bind:team="@{viewModel.result.awayTeam}" ObservableField
  • 58.
    58 Visibility <FrameLayout style="@style/progress_layout"
 android:visibility= "@{viewModel.loading ?View.VISIBLE : View.GONE}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:bind="http://schemas.android.com/tools">
 <data> <import type="android.view.View"/>
 <variable name="viewModel"
 type="it.droidcon.databinding.MatchResultViewModel"/> </data>
 <FrameLayout style="@style/main_container"> 
 <LinearLayout style="@style/root_layout">
 <!-- ... -->
 </LinearLayout>
 
 
 </FrameLayout>
 </layout>
  • 59.
    59 Visibility <FrameLayout style="@style/progress_layout"
 android:visibility= "@{viewModel.loading ?View.VISIBLE : View.GONE}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> @BindingConversion
 public static int convertBooleanToVisibility(boolean b) {
 return b ? View.VISIBLE : View.GONE;
 }___

  • 60.
    60 Visibility <FrameLayout style="@style/progress_layout"
 android:visibility="@{viewModel.loading}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> @BindingConversion
 publicstatic int convertBooleanToVisibility(boolean b) {
 return b ? View.VISIBLE : View.GONE;
 }___
 @BindingAdapter("visibleOrGone")
 public static void bindVisibleOrGone(View view, boolean b) {
 view.setVisibility(b ? View.VISIBLE : View.GONE);
 }____
  • 61.
    61 Visibility <FrameLayout style="@style/progress_layout"
 app:visibleOrGone="@{viewModel.loading}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> @BindingConversion
 publicstatic int convertBooleanToVisibility(boolean b) {
 return b ? View.VISIBLE : View.GONE;
 }___
 @BindingAdapter("visibleOrGone")
 public static void bindVisibleOrGone(View view, boolean b) {
 view.setVisibility(b ? View.VISIBLE : View.GONE);
 }____ @BindingAdapter("visible")
 public static void bindVisible(View view, boolean b) {
 view.setVisibility(b ? View.VISIBLE : View.INVISIBLE);
 }
  • 62.
    <LinearLayout style="@style/root_layout"
 android:onClick="@{???}">
 <!-- ...-->
 </LinearLayout>
 62 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
  • 63.
    public final_View.OnClickListener reloadClickListener= new View.OnClickListener() {
 @Override public void onClick(View v) {
 reload();
 }_
 };
 <LinearLayout style="@style/root_layout"
 android:onClick="@{viewModel.reloadClickListener}">
 <!-- ... -->
 </LinearLayout>
 63 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
  • 64.
    <LinearLayout style="@style/root_layout"
 android:onClick="@{viewModel::reload}">
 <!-- ...-->
 </LinearLayout>
 64 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload(View v) { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
  • 65.
    <LinearLayout style="@style/root_layout"
 android:onClick="@{v ->viewModel.reload()}">
 <!-- ... -->
 </LinearLayout>
 65 }___ public class MatchResultViewModel { public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__
  • 66.
    public class MatchResultViewModel{ public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__ 66 }___ @BindingAdapter("android:onClick")
 public static void bindOnClick(View view, final Runnable listener) {
 view.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) {
 listener.run();
 }____
 });
 }___ <LinearLayout style="@style/root_layout"
 android:onClick="@{v -> viewModel.reload()}">
 <!-- ... -->
 </LinearLayout>

  • 67.
    public class MatchResultViewModel{ public final ObservableField<MatchResult> result = new ObservableField<>();
 public final ObservableBoolean loading = new ObservableBoolean();
 public void reload() { loading.set(true);
 reloadInBackground(result -> {
 loading.set(false);
 this.result.set(result);
 });
 }__ <LinearLayout style="@style/root_layout"
 android:onClick="@{viewModel::reload}">
 <!-- ... -->
 </LinearLayout>
 67 }___ @BindingAdapter("android:onClick")
 public static void bindOnClick(View view, final Runnable listener) {
 view.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) {
 listener.run();
 }____
 });
 }___
  • 68.
    68 Final layout <?xml version="1.0"encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:bind="http://schemas.android.com/tools">
 <data>
 <!-- ... -->
 </data>
 <FrameLayout style="@style/main_container"> 
 <LinearLayout style="@style/root_layout"
 android:onClick=“@{viewModel::reload}”>
 <!-- ... -->
 </LinearLayout>
 
 <FrameLayout style="@style/progress_layout"
 app:visibleOrGone="@{viewModel.loading}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout>
 
 </FrameLayout>
 </layout>
  • 69.
    69 public interface Reloadable{
 String getErrorMessage();
 
 void reload();
 }
 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable name="reloadable"
 type="it.droidcon.databinding.Reloadable"/>
 </data>
 
 <LinearLayout style="@style/reload_layout">
 <TextView
 style="@style/error_message"
 android:text="@{reloadable.errorMessage}"/>
 
 <Button
 style="@style/reload"
 android:onClick="@{reloadable::reload}"
 android:text="@string/reload"/>
 </LinearLayout>
 </layout>
  • 70.
    70 Model View ViewModel View ViewModel Model DataBinding Retainedon configuration change Saved in Activity or Fragment state Activity or Fragment
  • 71.
    71 github.com/fabioCollini/mv2m Android MVVM lightweightlibrary based on Android Data Binding
  • 72.
    View ViewModel RetrofitService reload update binding Model ViewViewModel RetrofitServiceModel request response binding
  • 73.
  • 74.
  • 75.
  • 76.
    76 Data binding You canwrite all your business logic in an huge xml file ———————————————————————— ————————————
  • 77.
    Custom attributes ReusableUI code 77 Data binding You can write all your business logic in an huge xml file ———————————————————————— ———————————— Includes UI components MVVM Testable code
  • 78.
    78 Thanks for yourattention! androidavanzato.it Questions?
  • 79.
    79 Links developer.android.com/tools/data-binding/guide.html Is Databinding stillbeta? - Google plus medium.com/@fabioCollini/android-data-binding-f9f9d3afc761 github.com/fabioCollini/mv2m github.com/commit-non-javisti/CoseNonJavisteAndroidApp halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android