data binding in action using mvvm pattern

79
Android Data Binding in action using MVVM pattern Fabio Collini

Upload: fabio-collini

Post on 07-Jan-2017

2.807 views

Category:

Mobile


5 download

TRANSCRIPT

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

mat

ch_r

esul

t.xm

lM

atch

Resu

ltBin

ding

.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

Activityprivate 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 everywhereCan be any View

25

BindingConversion@BindingConversionpublic 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}"/>

@BindingConversionpublic 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));}__

@BindingConversionpublic 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));}__

@BindingConversionpublic 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

Text

Wat

cher

set(…

)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>

@BindingConversionpublic 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>

@BindingConversionpublic 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>

@BindingConversionpublic 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/mv2mAndroid MVVM lightweight library based on

Android Data Binding

View ViewModel RetrofitService

reload

updatebinding

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())

updatebinding

Model

requestresponse

EspressoTest

View ViewModel ModelEspressoTest

onView

verify

when().thenReturn()

reloadbinding

76

Data binding

You can write all your business logic in an huge xml file

————————————————————————————————————

Custom attributes Reusable UI code

77

Data bindingYou can write all your business logic

in an huge xml file————————————————————————

————————————

Includes UI componentsMVVM 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