Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Data Binding in Action using MVVM pattern

4,303 views

Published on

The Data Binding framework was one of Google’s announcements at I/O 2015, it’s a big change in the code organization of an Android app. Some developers are sceptical about this framework but, if used in the “right way”, it’s very powerful and it allows to remove a lot of redundant boilerplate code from activities and fragments.

In this talk we’ll start from the Data Binding basic concepts and then we’ll see how to use it to improve the architecture of a typical Android application applying the Model View ViewModel pattern. Using this pattern you need to write less code to create an app that can be easily tested using JVM and instrumentation tests.

Published in: Mobile
  • Hey guys! Who wants to chat with me? More photos with me here 👉 http://www.bit.ly/katekoxx
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Data Binding in Action using MVVM pattern

  1. 1. Android Data Binding in action using MVVM pattern Fabio Collini
  2. 2. 2 Ego slide @fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini codingjam.it
  3. 3. 3 Agenda 1. Data Binding basics 2. Custom attributes 3. Components 4. Two Way Data Binding 5. Model View ViewModel
  4. 4. 4 Example project
  5. 5. 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. 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. 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()));
 }
 }
  8. 8. DroidCon Italy - Torino - April 2016 - @fabioCollini 8 1Data Binding basics
  9. 9. 9 Google I/O 2015
  10. 10. 10 Is Data Binding still beta?
  11. 11. 11 Is Data Binding still beta?
  12. 12. dataBinding {
 enabled = true
 }
 12 build.gradle android {
 compileSdkVersion 23
 buildToolsVersion "23.0.2"
 
 defaultConfig {
 //...
 ____}
 buildTypes {
 //...
 ____} }
  13. 13. <?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>
  14. 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. 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. 16. 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. 17. 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. 18. 18 Code in XML? Are you serious?!?
  19. 19. 19 Complex code in XML is NOT a best practice
  20. 20. DroidCon Italy - Torino - April 2016 - @fabioCollini 20 2Custom attributes
  21. 21. 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. 22. 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. 23. 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}"/>
  24. 24. 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. 25. 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}"/>
  26. 26. @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)}"/>
  27. 27. <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}"/>

  28. 28. <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. 29. 29 FontBinding
  30. 30. DroidCon Italy - Torino - April 2016 - @fabioCollini 30 3Components
  31. 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>
 <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. 32. 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>
  33. 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. 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. 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. 36. DroidCon Italy - Torino - April 2016 - @fabioCollini 36 4Two Way Data Binding
  37. 37. 37
  38. 38. 38 public class ContactInfo {
 public String message;
 
 public boolean messageAvailable;
 }__
  39. 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. 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. 41. 41
  42. 42. 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. 43. 43 Observable hierarchy
  44. 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. 45 public class ContactInfo {
 public final ObservableField<String> message = new ObservableField<>();_
 
 public final ObservableBoolean messageAvailable = new ObservableBoolean();_
 }__
  46. 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. 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. 48. 48
  49. 49. 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. 50. 50
  51. 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. 52. 52
  53. 53. 53
  54. 54. 54 Layout ContactInfo Binding TextWatcherset(…) addOnProperty ChangedCallbackset(…) if (changed) WeakReference if (changed)
  55. 55. DroidCon Italy - Torino - April 2016 - @fabioCollini 55 5MVVM
  56. 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. 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>
 <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. 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. 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. 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. 61. 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);
 }
  62. 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. 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. 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. 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. 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. 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. 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. 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. 70 Model View ViewModel View ViewModel Model DataBinding Retained on configuration change Saved in Activity or Fragment state Activity or Fragment
  71. 71. 71 github.com/fabioCollini/mv2m Android MVVM lightweight library based on Android Data Binding
  72. 72. View ViewModel RetrofitService reload update binding Model View ViewModel RetrofitServiceModel request response binding
  73. 73. Testable code Data binding and MVVM
  74. 74. MockService MockService RetrofitService RetrofitServiceViewModel reload update Model request response JVM Test ViewModel ModelJVM Test assert when().thenReturn() verify
  75. 75. MockServiceRetrofitService MockServiceRetrofitService View ViewModel perform(click()) update binding Model request response EspressoTest View ViewModel ModelEspressoTest onView verify when().thenReturn() reload binding
  76. 76. 76 Data binding You can write all your business logic in an huge xml file ———————————————————————— ————————————
  77. 77. 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. 78. 78 Thanks for your attention! androidavanzato.it Questions?
  79. 79. 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

×