data binding in action using mvvm pattern
TRANSCRIPT
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
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())); }}
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);}_
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)}"/>
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" />
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(); }}
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); }}
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
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
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
54
Layout
ContactInfo
Binding
Text
Wat
cher
set(…
)addOnProperty
ChangedCallbackset(…)
if (changed)
WeakReference
if (changed)
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
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
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