O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Android Data Binding in action using MVVM pattern - droidconUK

3.122 visualizações

Publicada em

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.

Publicada em: Celular
  • Seja o primeiro a comentar

Android Data Binding in action using MVVM pattern - droidconUK

  1. 1. #droidconUK Android Data Binding in action using MVVM pattern Fabio Collini droidcon London October 2016
  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. Data Binding + RxJava 6. Model View ViewModel
  4. 4. #droidconUK - London - October 2016 - @fabioCollini 4 1Data Binding basics
  5. 5. Google I/O 2016 5 Google I/O 2015
  6. 6. 6 github.com/fabioCollini/DataBindingInAction
  7. 7. 7 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>
  8. 8. 8 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
 }
  9. 9. dataBinding {
 enabled = true
 }
 9 build.gradle android {
 //...
 //...
 
 defaultConfig {
 //...
 ____}
 buildTypes {
 //...
 ____} }
  10. 10. <?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> 10 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>
  11. 11. <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> 11 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;
 // ...
 }
  12. 12. 12 public class MatchResultActivity extends AppCompatActivity {
 
 private MatchResultBinding binding;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 binding = DataBindingUtil.setContentView(this, R.layout.match_result);
 
 MatchResult result = getIntent().getParcelableExtra("RESULT");
 
 if (result.getHomeTeam() != null) {
 binding.homeTeam.setText(result.getHomeTeam().getName());
 binding.homeGoals.setText( Integer.toString(result.getHomeTeam().getGoals()));
 }if1
 if (result.getAwayTeam() != null) {
 binding.awayTeam.setText(result.getAwayTeam().getName());
 binding.awayGoals.setText( Integer.toString(result.getAwayTeam().getGoals()));
 }if
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 }onCreate
 }activity
  13. 13. 13 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>
  14. 14. 14 public class MatchResultActivity extends AppCompatActivity {
 
 private MatchResultBinding binding;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 binding = DataBindingUtil.setContentView(this, R.layout.match_result);
 
 MatchResult result = getIntent().getParcelableExtra("RESULT");
 binding.setResult(result);
 if
 Glide.with(this).load(result.getGifUrl())
 .placeholder(R.drawable.loading).into(binding.resultGif);
 }onCreate
 }activity
  15. 15. 15 Code in XML? Are you serious?!?
  16. 16. 16 Complex code in XML is NOT a best practice
  17. 17. #droidconUK - London - October 2016 - @fabioCollini 17 2Custom attributes
  18. 18. 18 @BindingAdapter <ImageView android:id="@+id/result_gif" style="@style/gif"/> 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);
 }
  19. 19. 19 public class MatchResultActivity extends AppCompatActivity {
 
 private MatchResultBinding binding;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 
 binding = DataBindingUtil.setContentView( this, R.layout.match_result);
 
 MatchResult result = getIntent().getParcelableExtra("RESULT");
 binding.setResult(result);
 }onCreate
 }activity
  20. 20. 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
  21. 21. @BindingAdapter("goals")
 public static void bindGoals(TextView view, int goals) {
 view.setText(Integer.toString(goals));
 }__
 21 BindingAdapter <TextView style="@style/goals"
 android:text="@{Integer.toString(result.awayTeam.goals)}"/> <TextView style="@style/goals" app:goals="@{result.awayTeam.goals}"/>
  22. 22. #droidconUK - London - October 2016 - @fabioCollini 22 3Components
  23. 23. 23 <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>
  24. 24. 24 <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>
  25. 25. </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}"
  26. 26. #droidconUK - London - October 2016 - @fabioCollini 26 4Two Way Data Binding
  27. 27. 27
  28. 28. 28 public class QuestionInfo {
 public String answer = "";
 public int countdown = 10; public int decrementCountdown() {
 return --countdown;
 }_ }_
  29. 29. 29 public class QuestionInfo {
 public String answer = "";
 public int countdown = 10; public int decrementCountdown() {
 return --countdown;
 }_ }_ <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable
 name="info"
 type="it.droidcon.databinding.question.QuestionInfo"/>
 </data>
 
 <LinearLayout style="@style/form_root">
 <TextView style="@style/question"/>
 
 <EditText style="@style/answer" 
 android:text="@{info.answer}" />
 
 <Button style="@style/form_button"
 android:enabled="@{info.countdown > 0 &amp;&amp; !info.answer.empty}"/>
 
 <TextView style="@style/countdown"
 android:text="@{Integer.toString(info.countdown)}" />
 </LinearLayout>
 </layout>
  30. 30. public class QuestionActivity extends AppCompatActivity {
 private QuestionInfo info;
 
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 QuestionBinding binding = DataBindingUtil.setContentView(this, R.layout.question);
 info = new QuestionInfo();
 binding.setInfo(info); Handler handler = new Handler();
 handler.postDelayed(new Runnable() {
 @Override public void run() {
 int newValue = info.decrementCountdown();
 if (newValue > 0) {
 handler.postDelayed(this, 1000);
 }
 }
 }, 1000);
 } } 30 public class QuestionInfo {
 public String answer = "";
 public int countdown = 10; public int decrementCountdown() {
 return --countdown;
 }_ }_
  31. 31. 31
  32. 32. 32 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);
 }
 }
  33. 33. 33 Observable hierarchy
  34. 34. 34 public class QuestionInfo extends BaseObservable {_
 private String answer = "";
 
 private int countdown = 10;
 
 public int decrementCountdown() {
 --countdown;
 notifyPropertyChanged(BR.countdown);
 return countdown;
 }__
 
 @Bindable public String getAnswer() {
 return answer;
 }getAnswer
 
 public void setAnswer(String answer) {
 this.answer = answer;
 notifyPropertyChanged(BR.answer);
 }setAnswer
 
 @Bindable public int getCountdown() {
 return countdown;
 }getCountdown
 }___
  35. 35. 35 public class QuestionInfo {_
 public final ObservableField<String> answer = new ObservableField<>("");
 
 public final ObservableInt countdown = new ObservableInt(10);
 
 public int decrementCountdown() {
 int value = countdown.get() - 1;
 countdown.set(value);
 return value;
 }__
 }___
  36. 36. <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable
 name="info"
 type="it.droidcon.databinding.question.QuestionInfo"/>
 </data>
 
 <LinearLayout style="@style/form_root">
 <TextView style="@style/question"/>
 
 <EditText style="@style/answer" 
 android:text="@{info.answer}" />
 
 <Button style="@style/form_button"
 android:enabled="@{info.countdown > 0 &amp;&amp; !info.answer.empty}"/>
 
 <TextView style="@style/countdown"
 android:text="@{Integer.toString(info.countdown)}" />
 </LinearLayout>
 </layout> 36 ObservableField<String> ObservableInt ObservableInt public class QuestionInfo {_
 public final ObservableField<String> answer = new ObservableField<>("");
 
 public final ObservableInt countdown = new ObservableInt(10);
 
 public int decrementCountdown() {
 int value = countdown.get() - 1;
 countdown.set(value);
 return value;
 }__
 }___ ObservableField<String>
  37. 37. 37
  38. 38. 38 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
  39. 39. 39
  40. 40. <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable
 name="info"
 type="it.droidcon.databinding.question.QuestionInfo"/>
 </data>
 
 <LinearLayout style="@style/form_root">
 <TextView style="@style/question"/>
 
 <EditText style="@style/answer" 
 android:text="@={info.answer}" />
 
 <Button style="@style/form_button"
 android:enabled="@{info.countdown > 0 &amp;&amp; !info.answer.empty}"/>
 
 <TextView style="@style/countdown"
 android:text="@{Integer.toString(info.countdown)}" />
 </LinearLayout>
 </layout> 40 Two way data binding
  41. 41. 41
  42. 42. 42
  43. 43. 43 Layout QuestionInfo Binding TextWatcherset(…) addOnProperty ChangedCallbackset(…) if (changed) WeakReference if (changed)
  44. 44. #droidconUK - London - October 2016 - @fabioCollini 44 5Data Binding + RxJava
  45. 45. 45 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 <data>
 <variable
 name="info"
 type="it.droidcon.databinding.question.QuestionInfo"/>
 </data>
 
 <LinearLayout style="@style/form_root">
 <TextView style="@style/question"/>
 
 <EditText style="@style/answer" 
 android:text="@={info.answer}" />
 
 <Button style="@style/form_button"
 android:enabled="@{info.countdown > 0 &amp;&amp; !info.answer.empty}"/>
 
 <TextView style="@style/countdown"
 android:text="@{Integer.toString(info.countdown)}" />
 </LinearLayout>
 </layout>
  46. 46. public class QuestionInfo extends BaseObservable {_
 private String answer = "";
 private int countdown = 10;
 
 public int decrementCountdown() {
 --countdown;
 notifyPropertyChanged(BR.countdown); notifyPropertyChanged(BR.sendEnabled);
 return countdown;
 }__
 
 @Bindable public String getAnswer() {
 return answer;
 }getAnswer
 
 public void setAnswer(String answer) {
 this.answer = answer;
 notifyPropertyChanged(BR.answer);
 notifyPropertyChanged(BR.sendEnabled);
 }setAnswer
 
 @Bindable public int getCountdown() {
 return countdown;
 }getCountdown @Bindable public boolean isSendEnabled() {
 return !answer.isEmpty() && countdown > 0;
 }isButtonEnabled
 }___
  47. 47. 47 Not an Observable, View is not updated! public class QuestionInfo {_
 public final ObservableField<String> answer = new ObservableField<>("");
 
 public final ObservableInt countdown = new ObservableInt(10);
 
 public int decrementCountdown() {
 int value = countdown.get() - 1;
 countdown.set(value);
 return value;
 }__ public boolean isSendEnabled() {
 return !answer.get().isEmpty() && countdown.get() > 0;
 }
 }___
  48. 48. 48 RxJava FTW!
  49. 49. ObservableField<T> rx.Observable<T>
  50. 50. 50 ObservableField<T> rx.Observable<T> public static <T> rx.Observable<T> toRx(ObservableField<T> observableField) {
 return rx.Observable.fromEmitter(emitter -> {
 emitter.onNext(observableField.get());
 OnPropertyChangedCallback callback = new OnPropertyChangedCallback() {
 @Override
 public void onPropertyChanged(Observable observable, int i) {
 emitter.onNext(((ObservableField<T>) observable).get());
 }
 };
 observableField.addOnPropertyChangedCallback(callback);
 emitter.setCancellation(() -> observableField.removeOnPropertyChangedCallback(callback));
 }, Emitter.BackpressureMode.BUFFER);
 }
  51. 51. 51 public class QuestionInfo {_
 public final ObservableField<String> answer = new ObservableField<>("");
 
 public final ObservableInt countdown = new ObservableInt(10); public final ObservableBoolean sendEnabled = new ObservableBoolean();
 
 public int decrementCountdown() {
 int value = countdown.get() - 1;
 countdown.set(value);
 return value;
 }__ }___
  52. 52. 52 public class QuestionActivity extends AppCompatActivity {
 
 private QuestionInfo info;
 
 private Subscription subscription;
 
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
 //...
 }
 
 @Override protected void onStart() {
 super.onStart();
 subscription = Observable.combineLatest(
 toRx(info.answer),
 toRx(info.countdown),
 (answer, countdown) ->
 !answer.isEmpty() && countdown > 0
 ).subscribe(info.sendEnabled::set);
 }
 
 @Override protected void onStop() {
 super.onStop();
 subscription.unsubscribe();
 }
 }
  53. 53. 53 compile 'com.cantrowitz:rxbroadcast:1.0.0' public class ConnectionChecker {
 
 private Context context;
 
 public ConnectionChecker(Context context) {
 this.context = context;
 }
 
 public Observable<Boolean> getConnectionStatus() {
 IntentFilter filter = new IntentFilter( ConnectivityManager.CONNECTIVITY_ACTION);
 return RxBroadcast.fromBroadcast(context, filter)
 .map(i -> getNetworkInfo())
 .map(info -> info != null && info.isConnected())
 .distinctUntilChanged();
 }
 
 private NetworkInfo getNetworkInfo() {
 ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
 return connectivityManager.getActiveNetworkInfo();
 }
 }
  54. 54. public class QuestionActivity extends AppCompatActivity {
 
 private QuestionInfo info;
 
 private Subscription subscription;
 
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
 //...
 }onCreate
 
 @Override protected void onStart() {
 super.onStart();
 subscription = Observable.combineLatest(
 toRx(info.answer),
 toRx(info.countdown),
 connectionChecker.getConnectionStatus(),
 (answer, countdown, connected) ->
 !answer.isEmpty() && countdown > 0 && connected
 ).subscribe(info.sendEnabled::set);
 }onStart
 
 @Override protected void onStop() {
 super.onStop();
 subscription.unsubscribe();
 }onStop
 }_ 54
  55. 55. #droidconUK - London - October 2016 - @fabioCollini 55 6MVVM
  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"
 app:visibleOrGone="@{viewModel.loading}">
 <ProgressBar style="@style/progress"/>
 </FrameLayout> @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);
 }
  60. 60. <LinearLayout style="@style/root_layout"
 android:onClick="@{???}">
 <!-- ... -->
 </LinearLayout>
 60 }___ 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);
 });
 }__
  61. 61. <LinearLayout style="@style/root_layout"
 android:onClick="@{v -> viewModel.reload()}">
 <!-- ... -->
 </LinearLayout>
 61 public void reload() { //..
 }__ <LinearLayout style="@style/root_layout"
 android:onClick=“@{viewModel::reload}”>
 <!-- ... -->
 </LinearLayout>
 public void reload(View v) { //..
 }__ @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=“@{viewModel::reload}”>
 <!-- ... -->
 </LinearLayout>
 public void reload() { //..
 }__
  62. 62. 62 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>
  63. 63. 63 Model View ViewModel View ViewModel Model DataBinding Retained on configuration change Saved in Activity or Fragment state Activity or Fragment
  64. 64. MVVM View ViewModel Model DataBinding View Presenter Model MVPVs
  65. 65. MVVM MVPVs Less Java code if (view != null) A/B testing on View Sometimes we need an Activity :( Testable code Testable code Less XML No more
  66. 66. 66 github.com/fabioCollini/LifeCycleBinder Move your Android code to testable Java classes
  67. 67. Custom attributes Reusable UI code 67 Data binding Includes UI components RxJava Easy composition
  68. 68. 68 Links developer.android.com/tools/data-binding/guide.html Google I/O 2015 - What's new in Android Data Binding -- Write Apps Faster (Android Dev Summit 2015) Advanced Data Binding - Google I/O 2016 George Mount medium profile Radosław Piekarz: RxJava meets Android Data Binding Florina Muntenescu: A Journey Through MV Wonderland Bill Phillips: Shades of MVVM
  69. 69. 69 Thanks for your attention! Questions? This presentation will be soon available on the droidcon London website at uk.droidcon.com/#skillscasts

×