SlideShare uma empresa Scribd logo
1 de 73
Baixar para ler offline
Effective Android Data Binding
Eric Maxwell

Product Engineer @ Realm
Android, iOS Developer
What are you going to learn?
• Introduction & Getting Started
• Data Binding Features & Use Cases
• Data Binding + MVVM
• ViewModels and LiveData
• Tips from using Data Binding in Production
Data Binding
https://github.com/ericmaxwell2003/DataBindingDemos/
Counter Fancy CounterLoginForm RecyclerView
Data Binding Intro
• Support Library to bind data directly to your views
• Views can automatically update when underlying data changes
• Views can connect directly to actions
DATA
TADA
Data
Data
Data
Enable Databinding
android {
...
dataBinding {
enabled = true
}
}
To enable data binding, add this to your app module build.gradle file…
Traditional Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android"
...>
<EditText
android:id="@+id/userNameEditText" />
<Button
android:onClick=‘sayHiTo'/>
</LinearLayout>
Data binding Layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewModel" type="com.acme.ViewModel" />
</data>
<LinearLayout...
<EditText
android:id="@+id/userNameEditText"
android:text="@{viewModel.initialUserName}" />
<Button
android:text="@string/say_hi"
android:onClick='@{() -> viewModel.sayHiTo(userNameEditText)}'
/>
</LinearLayout>
</layout>
Converting Existing Layouts
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android"
...>
<EditText
android:id="@+id/userNameEditText" />
<Button
android:onClick=‘sayHiTo'/>
</LinearLayout>
Data binding Layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewModel" type="com.acme.ViewModel" />
</data>
<LinearLayout...
<EditText
android:id="@+id/userNameEditText"
android:text="@{viewModel.initialUserName}" />
<Button
android:text="@string/say_hi"
android:onClick='@{() -> viewModel.sayHiTo(userNameEditText)}'
/>
</LinearLayout>
</layout>
Getting Started
Example Covers
• How to enable data binding
• Building Layouts
• Binding View Model
• Data
• Actions
https://github.com/ericmaxwell2003/DataBindingDemos/
Layout
<?xml version="1.0" encoding="utf-8"?>
<layout ...>
<data>
<variable name="viewModel" 

type="com.acme.counter.CounterViewModel" />
</data>
<android.support.constraint.ConstraintLayout ...>
<TextView
android:id="@+id/count"
tools:text=“0"
android:text="@{String.valueOf(viewModel.count)}"
/>
<Button
android:id="@+id/decrement"
android:onClick="@{() -> viewModel.decrement()}"
/>
<Button
android:id="@+id/increment"
android:onClick="@{() -> viewModel.increment()}"
/>
</android.support.constraint.ConstraintLayout>
</layout>
Invoke decrement() on click
Invoke increment() on click
Observing value of count property
public class CounterViewModel extends BaseObservable {
private int count = 0;
public void increment() {
setCount(getCount() + 1);
}
public void decrement() {
setCount(getCount() - 1);
}
@Bindable
public int getCount() {
return count;
}
private void setCount(int count) {
this.count = count;
notifyPropertyChanged(BR.count);
}
}
ViewModel
@Bindable annotation makes this 

property eligible for binding
Triggers view observing count to update
public class CounterActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCounterBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_counter);
binding.setViewModel(new CounterViewModel());
}
}
Activity
Generated from activity_counter.xml
Activity
public class CounterActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCounterBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_counter);
binding.setViewModel(new CounterViewModel());
}
}
Connect ViewModel to View
Activity
public class CounterActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCounterBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_counter);
binding.setViewModel(new CounterViewModel());
}
}
<data>
<variable name="viewModel" type="com.acme.counter.CounterViewModel" />
</data>
Layout variable declaration defines viewModel name
public class CounterActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCounterBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_counter);
binding.setViewModel(new CounterViewModel());
}
}
<data>
<variable name="viewModel" type="com.acme.counter.CounterViewModel" />
</data>
Activity
Layout variable declaration defines viewModel type
public class CounterViewModelTest {
private CounterViewModel counterViewModel;
@Before public void setup() { counterViewModel = new CounterViewModel(); }
@Test
public void testInitialState() { Assert.assertEquals(0, counterViewModel.getCount()); }
@Test
public void testIncrement() {
for (int i = 1; i <= 5; i++) {
counterViewModel.increment();
Assert.assertEquals(i, counterViewModel.getCount());
}
}
@Test
public void testDecrement() {
for (int i = 1; i <= 5; i++) {
counterViewModel.decrement();
Assert.assertEquals(i * -1, counterViewModel.getCount());
}
}
}
Testing data bound view models
Closer Look
DATA
TADA
Data
Data
Data
Inside <data> Tag
<data>
<!—- (Define 0..* variables) -—>
<variable name="viewModel" type="com.acme.ViewModel" />
<!—- (Define 0..* imports) -—>
<import type="android.view.View" />
</data>
Variables
• Define name and type of variable
• Value set by hosting Activity/Fragment
Imports
• Define imports similar to imports for in classes
• Import static -Utils classes, View, etc.
• Example usages
android:visibility="@{someCondition ? View.VISIBLE : View.GONE}"
android:text='@{StringUtils.suffixWithAwesome("Everything is ")'
Expression Syntax
"@{expression}"
Expression Syntax
'@{expression}'
Expression Syntax
'@{data.method("constant")}'
'@{Utils.staticMethod()}'
Expression Syntax
'@{Utils.staticValue}'
Expression Syntax
‘@{@string/stringsRes}'
Expression Syntax
‘@{viewModel.value}'
Where Value will resolve to (in order of precedence)
• getValue() // Property Accessor
• value() // Method with that name
• value // Public property
Expression Resolution
‘@{viewModel.value.subval}’
Expressions are Null Safe
Expressions are null safe
• viewModel == null // Okay!
‘@{viewModel.value.subval}’
Expressions are null safe
• viewModel == null // Okay!
• viewModel.value == null // I eat null for breakfast!
Expressions are Null Safe
‘@{viewModel.value.subval}’
Expressions are null safe
• viewModel == null // Okay!
• viewModel.value == null // I eat null for breakfast!
• viewModel.value.subval == null // NPE!
Expressions are Null Safe
‘@{viewModel.value.subval}’
Expressions are null safe
• viewModel == null // Okay!
• viewModel.value == null // I eat null for breakfast!
• viewModel.value.subval == null // … j/k you’re fine!
Expressions are Null Safe
‘@{viewModel.value.subval ?? “Foo”}’
Expressions are null safe
• viewModel == null // Okay!
• viewModel.value == null // I eat null for breakfast!
• viewModel.value.subval == null // … j/k you’re fine!
Expressions are Null Safe
Observables
DATA
TADA
Data
Data
Data
public class CounterViewModel extends BaseObservable {
private int count = 0;
public void increment() {
setCount(getCount() + 1);
}
public void decrement() {
setCount(getCount() - 1);
}
@Bindable
public int getCount() {
return count;
}
private void setCount(int count) {
this.count = count;
notifyPropertyChanged(BR.count);
}
}
Making Data Observable
Base

Observable
Observable

ViewModel
<<Observable>>
Counter

ViewModel
public class CounterViewModel extends BaseObservable {
private int count = 0;
public void increment() {
setCount(getCount() + 1);
// perform many other operations

// update many data bound variables

notifyChange();
}
public void decrement() {
setCount(getCount() - 1);
}
@Bindable
public int getCount() {
return count;
}
private void setCount(int count) {
this.count = count;
}
}
Making Data Observable
Base

Observable
Observable

ViewModel
<<Observable>>
Counter

ViewModel
public interface Observable {
void addOnPropertyChangedCallback(...);
void removeOnPropertyChangedCallback(...);
public abstract static class OnPropertyChangedCallback {
public abstract void onPropertyChanged(...);
}
}
Making Data Observable
Base

Observable
Observable

ViewModel
<<Observable>>
Counter

ViewModel
/**
* Bridge class which does everything android.databinding.BaseObservable
does plus
* extends Android Architecture Components ViewModel
*/
public abstract class ObservableViewModel
extends ViewModel
implements Observable {
...
}
How to make an Observable ViewModel
Base

Observable
Observable

ViewModel
<<Observable>>
Counter

ViewModel
public class CounterViewModel extends ObservableViewModel {
private int count = 0;
public void increment() {
setCount(getCount() + 1);
}
public void decrement() {
setCount(getCount() - 1);
}
@Bindable
public int getCount() {
return count;
}
private void setCount(int count) {
this.count = count;
notifyPropertyChanged(BR.count);
}
}
Base

Observable
Observable

ViewModel
<<Observable>>
Counter

ViewModel
How to make an Observable ViewModel
public class CounterViewModel extends ViewModel {
public ObservableInt count = new ObservableInt(0);
public void increment() {
count.set(count.get() + 1);
}
public void decrement() {
count.set(count.get() - 1);
}
}
Other Options
Other Options
Observable Primitives: ObservableInt, ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableFloat
Observable Collections: ObservableArrayMap, ObservableArrayList
public class CounterViewModel extends ViewModel {
public ObservableInt count = new ObservableInt(0);
public void increment() {
count.set(count.get() + 1);
}
public void decrement() {
count.set(count.get() - 1);
}
}
Other Options
Observable Primitives: ObservableInt, ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableFloat
Observable Collections: ObservableArrayMap, ObservableArrayList
public class CounterViewModel extends ViewModel {
public ObservableInt count = new ObservableInt(0);
public void increment() {
count.set(count.get() + 1);
}
public void decrement() {
count.set(count.get() - 1);
}
}
Best used for simpler views 

with only a few observable fields
public class CounterViewModel extends ObservableViewModel {
private int count = 0;
public void increment() {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
count++;
notifyPropertyChanged(BR.countLabel);
Log.i(TAG, "Counter [" + count + "], on bg thread");
}
});
}
...
}
Update can happen in background threads
You can change your data model in a background thread as long as it is not a collection. 



https://developer.android.com/topic/libraries/data-binding/index.html#advanced_binding
2 Way Binding
DATA
TADA
Data
Data
Data
1 Way Data Binding
'@{expressions}'
2 Way Data Binding
'@={expressions}'
2 Way Data Binding
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="vm" type="com.acme.loginform.LoginViewModel" />
</data>
<LinearLayout...>
...
<EditText
android:id="@+id/userTextView"
android:hint="@string/login_username_hint"
tools:text=“user@acme.com”
android:text=“@={vm.username}"/>
<EditText
android:id="@+id/passTextView"
android:hint="@string/login_password_hint"
tools:text="password"
android:text=“@={vm.password}”/>
<Button
android:id="@+id/login_button"
android:onClick="@{() -> vm.login()}"
android:text="@string/connect_button_label" />
</LinearLayout>
</layout>
Username can be pre-populated from last username
2 Way Data Binding
public class LoginViewModel extends ViewModel {
public String username = sharedPrefs.getLastUsername();
public String password = "";
public void login() {
updateLastUsedUsername();
loginService.attemptLogin(username, password,
new FakeLoginService.LoginCallback() {
...
}
);
}
private void updateLastUsedUsername() {
sharedPrefs.setLastUsername(username);
}
}
2 Way Data Binding
public class LoginViewModel extends ViewModel {
public String username = sharedPrefs.getLastUsername();
public String password = "";
public void login() {
updateLastUsedUsername();
loginService.attemptLogin(username, password,
new FakeLoginService.LoginCallback() {
...
}
);
}
private void updateLastUsedUsername() {
sharedPrefs.setLastUsername(username);
}
}
BindingAdapter
+

BindingConverter
DATA
TADA
Data
Data
Data
Binding Adapters
@BindingAdapter({"app:imageUrl"})
public static void loadImage(ImageView view, String url) {
Picasso.with(view.getContext())
.load(url)
.into(view);
}
<ImageView
android:id="@+id/imageView"
tools:src="@drawable/design_time_sample_image"
app:imageUrl=‘@{"http://acme.com/images/any.png"}' />
Binding Adapters allow you to add custom attributes to views
Binding Adapters
@BindingAdapter({"app:imageUrl"})
public static void loadImage(ImageView view, String url) {
Picasso.with(view.getContext())
.load(url)
.into(view);
}
<ImageView
android:id="@+id/imageView"
tools:src="@drawable/design_time_sample_image"
app:imageUrl=‘@{"http://acme.com/images/any.png"}' />
Adds app:imageUrl attribute
Binding Adapters
@BindingAdapter({"app:imageUrl"})
public static void loadImage(ImageView view, String url) {
Picasso.with(view.getContext())
.load(url)
.into(view);
}
<ImageView
android:id="@+id/imageView"
tools:src="@drawable/design_time_sample_image"
app:imageUrl=‘@{"http://acme.com/images/any.png"}' />
Applies to ImageView
Binding Adapters
@BindingAdapter({"app:imageUrl"})
public static void loadImage(View view, String url) {
Picasso.with(view.getContext())
.load(url)
.into((ImageView)view);
}
<ImageView
android:id="@+id/imageView"
tools:src="@drawable/design_time_sample_image"
app:imageUrl=‘@{"http://acme.com/images/any.png"}' />
Applies to all Views
Binding Adapters
@BindingAdapter({"app:imageUrl"})
public static void loadImage(TextView view, String url) {
}
<ImageView
android:id="@+id/imageView"
tools:src="@drawable/design_time_sample_image"
app:imageUrl=‘@{"http://acme.com/images/any.png"}' />
Binding Adapters allow you to add custom attributes to views
Cannot find the setter for attribute 'app:imageUrl' with parameter type java.lang.String on android.widget.ImageView
Binding Adapters
@BindingAdapter({"app:imageUrl"})
public static void loadImage(ImageView view, String url) {
Picasso.with(view.getContext())
.load(url)
.into(view);
}
<ImageView
android:id="@+id/imageView"
tools:src="@drawable/design_time_sample_image"
app:imageUrl=‘@{"http://acme.com/images/any.png"}' />
Value of expression 

Must be an expression!
@BindingAdapter({"app:imageUrl", "app:placeholder"})
public static void loadImage(ImageView view, String url, Drawable placeholder) {
Picasso.with(view.getContext())
.load(url)
.placeholder(placeholder)
.into(view);
}
<ImageView
android:id="@+id/imageView"
tools:src="@drawable/design_time_sample_image"
app:imageUrl='@{“http://acme.com/images/any.png"}'
app:placeHolder="@{@drawable/ic_launcher_background}"
/>
Binding Adapters
@BindingAdapter({"android:visibility"})
public static void visibility(final View view,
final int newVisibility) {
// Do something like animate or log visibility change,…
view.setVisibility(newVisibility);
}
Override System Attribute
Good example @
https://medium.com/google-developers/android-data-binding-animations-55f6b5956a64
Converters
<ImageView
android:id="@+id/imageView"
app:imageUrl='@{“http://acme.com/images/any.png"}'
app:placeHolder="@{@drawable/ic_launcher_background}"
android:visibility=“@{viewModel.showImage ? View.VISIBLE : View.GONE}” />
Instead of this…
Converters
<ImageView
android:id="@+id/imageView"
app:imageUrl='@{“http://acme.com/images/any.png"}'
app:placeHolder="@{@drawable/ic_launcher_background}"
android:visibility="@{viewModel.showImage}" />
@BindingConversion
public static int convertBooleanToVisibility(boolean expression) {
return expression ? View.VISIBLE : View.GONE;
}
…you can do this
Applies for all attributes where boolean expression is given and int is required
Binding RecyclerViews
DATA
TADA
Data
Data
Data
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater =
LayoutInflater.from(parent.getContext());
MyRecyclerItemBinding itemBinding =
MyRecyclerItemBinding.inflate(layoutInflater, parent, false);
return new ViewHolder(itemBinding);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ItemViewModel item = items.get(position);
holder.bind(item);
}
class ViewHolder extends RecyclerView.ViewHolder {
private MyRecyclerItemBinding itemBinding;
ViewHolder(MyRecyclerItemBinding itemBinding) {
super(itemBinding.getRoot());
this.itemBinding = itemBinding;
}
void bind(ItemViewModel viewModel) {
itemBinding.setItemViewModel(viewModel);
itemBinding.executePendingBindings();
}
}
Recycler View Adapter
LiveData
DATA
TADA
Data
Data
Data
2 Way Data Binding
public class LoginViewModel extends ViewModel {
...
public MutableLiveData<State> state = new MutableLiveData<>();
public MutableLiveData<String> error = new MutableLiveData<>();
public void login() {
state.postValue(State.ATTEMPTING_LOGIN);
updateLastUsedUsername();
loginService.attemptLogin(username, password,
new FakeLoginService.LoginCallback() {
@Override
public void onSuccess(FakeLoginService.User user) {
state.postValue(State.AUTHENTICATED);
}
@Override
public void onFailure(Throwable e) {
error.postValue("Login Error!!");
state.postValue(State.WAITING_USER);
}
});
}
}
2 Way Data Binding
public class LoginViewModel extends ViewModel {
...
public MutableLiveData<State> state = new MutableLiveData<>();
public MutableLiveData<String> error = new MutableLiveData<>();
public void login() {
state.postValue(State.ATTEMPTING_LOGIN);
updateLastUsedUsername();
loginService.attemptLogin(username, password,
new FakeLoginService.LoginCallback() {
@Override
public void onSuccess(FakeLoginService.User user) {
state.postValue(State.AUTHENTICATED);
}
@Override
public void onFailure(Throwable e) {
error.postValue("Login Error!!");
state.postValue(State.WAITING_USER);
}
});
}
}
Observing Live Data
viewModel.state.observe(this, new Observer<LoginViewModel.State>() {
@Override
public void onChanged(@Nullable LoginViewModel.State state) {
if(state == null) { return; }
switch (state) {
case WAITING_USER:
hideProgress();
break;
case ATTEMPTING_LOGIN:
showProgress();
break;
case AUTHENTICATED:
authSuccessful();
break;
}
}
});
Observing Live Data
viewModel.error.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String errorMessage) {
if(!TextUtils.isEmpty(errorMessage)) {
showMessage(errorMessage);
}
}
});
Demo
DATA
TADA
Data
Data
Data
Tips
1. Avoid adding logic to your expressions, put that in the ViewModel

android:text='@{v.someCondition() ? @string/foo : @string/boo}' 

android:onClick='@{() -> someCondition() ? doSomething() : void}'
1. Avoid adding logic to your expressions, put that in the ViewModel
2. Data Binding works really well with MVVM
Tips
1. Avoid adding logic to your expressions, put that in the ViewModel
2. Data Binding works really well with MVVM
3. Make heavy use of tools attributes so you can see an example in your preview

android:text=‘@{String.valueOf(vm.count)}’ 

tools:text=‘0’
Tips
1. Avoid adding logic to your expressions, put that in the ViewModel
2. Data Binding works really well with ViewModels
3. Make heavy use of tools attributes so you can see an example in your preview
4. Custom attributes (BindingAdapters) can greatly simplify creating your views
Tips
1. Avoid adding logic to your expressions, put that in the ViewModel
2. Data Binding works really well with ViewModels
3. Make heavy use of tools attributes so you can see an example in your preview
4. Custom attributes can greatly simplify creating your views, use them!
5. Run often in development cycles, it can be difficult to track down build time
errors in your layouts
•See tip #1
Tips
Additional Materials
• Other great talk on Data Binding
• https://academy.realm.io/posts/droidkaigi-kevin-pelgrims-data-real-world-data-binding/
• https://academy.realm.io/posts/360-andev-2017-ben-mccanny-data-binding/
• https://academy.realm.io/posts/data-binding-android-boyar-mount/
• Great how to articles
• https://medium.com/@georgemount007
• Samples
• https://github.com/ericmaxwell2003/DataBindingDemos
• https://github.com/ericmaxwell2003/ticTacToe/tree/mvvm
Eric Maxwell
em@realm.io
github.com/ericmaxwell2003
@emmax
Thank you!

Mais conteúdo relacionado

Mais procurados

JQuery New Evolution
JQuery New EvolutionJQuery New Evolution
JQuery New Evolution
Allan Huang
 
Windows 8 Training Fundamental - 1
Windows 8 Training Fundamental - 1Windows 8 Training Fundamental - 1
Windows 8 Training Fundamental - 1
Kevin Octavian
 

Mais procurados (20)

"Android Data Binding в массы" Михаил Анохин
"Android Data Binding в массы" Михаил Анохин"Android Data Binding в массы" Михаил Анохин
"Android Data Binding в массы" Михаил Анохин
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
 
Михаил Анохин "Data binding 2.0"
Михаил Анохин "Data binding 2.0"Михаил Анохин "Data binding 2.0"
Михаил Анохин "Data binding 2.0"
 
JQuery New Evolution
JQuery New EvolutionJQuery New Evolution
JQuery New Evolution
 
Android Testing
Android TestingAndroid Testing
Android Testing
 
Data binding в массы!
Data binding в массы!Data binding в массы!
Data binding в массы!
 
Windows 8 Training Fundamental - 1
Windows 8 Training Fundamental - 1Windows 8 Training Fundamental - 1
Windows 8 Training Fundamental - 1
 
Wicket KT part 2
Wicket KT part 2Wicket KT part 2
Wicket KT part 2
 
Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019
 
How to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI ComponentsHow to Mess Up Your Angular UI Components
How to Mess Up Your Angular UI Components
 
I os 11
I os 11I os 11
I os 11
 
Swift Tableview iOS App Development
Swift Tableview iOS App DevelopmentSwift Tableview iOS App Development
Swift Tableview iOS App Development
 
Before there was Hoop Dreams, there was McDonald's: Strange and Beautiful
Before there was Hoop Dreams, there was McDonald's: Strange and BeautifulBefore there was Hoop Dreams, there was McDonald's: Strange and Beautiful
Before there was Hoop Dreams, there was McDonald's: Strange and Beautiful
 
Mad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screeningsMad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screenings
 
NongBeer MVP Demo application
NongBeer MVP Demo applicationNongBeer MVP Demo application
NongBeer MVP Demo application
 
Hacking Your Way To Better Security
Hacking Your Way To Better SecurityHacking Your Way To Better Security
Hacking Your Way To Better Security
 
Hacking Your Way to Better Security - PHP South Africa 2016
Hacking Your Way to Better Security - PHP South Africa 2016Hacking Your Way to Better Security - PHP South Africa 2016
Hacking Your Way to Better Security - PHP South Africa 2016
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
 
Handling action bar in Android
Handling action bar in AndroidHandling action bar in Android
Handling action bar in Android
 
I os 03
I os 03I os 03
I os 03
 

Semelhante a Effective Android Data Binding

Semelhante a Effective Android Data Binding (20)

Deep dive into Android Data Binding
Deep dive into Android Data BindingDeep dive into Android Data Binding
Deep dive into Android Data Binding
 
MVVM & Data Binding Library
MVVM & Data Binding Library MVVM & Data Binding Library
MVVM & Data Binding Library
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM pattern
 
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
 
Data Binding
Data BindingData Binding
Data Binding
 
Asp.NET MVC
Asp.NET MVCAsp.NET MVC
Asp.NET MVC
 
Magento Indexes
Magento IndexesMagento Indexes
Magento Indexes
 
Backbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVCBackbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVC
 
JavaScript Refactoring
JavaScript RefactoringJavaScript Refactoring
JavaScript Refactoring
 
Data Binding - Android by Harin Trivedi
Data Binding - Android by Harin TrivediData Binding - Android by Harin Trivedi
Data Binding - Android by Harin Trivedi
 
Building Modern Websites with ASP.NET by Rachel Appel
Building Modern Websites with ASP.NET by Rachel AppelBuilding Modern Websites with ASP.NET by Rachel Appel
Building Modern Websites with ASP.NET by Rachel Appel
 
SE2016 Android Mikle Anokhin "Speed up application development with data bind...
SE2016 Android Mikle Anokhin "Speed up application development with data bind...SE2016 Android Mikle Anokhin "Speed up application development with data bind...
SE2016 Android Mikle Anokhin "Speed up application development with data bind...
 
Asp.net mvc training
Asp.net mvc trainingAsp.net mvc training
Asp.net mvc training
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
SOLID Principles
SOLID PrinciplesSOLID Principles
SOLID Principles
 
AngularJs-training
AngularJs-trainingAngularJs-training
AngularJs-training
 
Dialogs in Android MVVM (14.11.2019)
Dialogs in Android MVVM (14.11.2019)Dialogs in Android MVVM (14.11.2019)
Dialogs in Android MVVM (14.11.2019)
 
Architecture components - IT Talk
Architecture components - IT TalkArchitecture components - IT Talk
Architecture components - IT Talk
 
Architecture Components
Architecture Components Architecture Components
Architecture Components
 
Getting the Most Out of jQuery Widgets
Getting the Most Out of jQuery WidgetsGetting the Most Out of jQuery Widgets
Getting the Most Out of jQuery Widgets
 

Último

Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Cara Menggugurkan Kandungan 087776558899
 

Último (6)

Leading Mobile App Development Companies in India (2).pdf
Leading Mobile App Development Companies in India (2).pdfLeading Mobile App Development Companies in India (2).pdf
Leading Mobile App Development Companies in India (2).pdf
 
Satara Call girl escort *74796//13122* Call me punam call girls 24*7hour avai...
Satara Call girl escort *74796//13122* Call me punam call girls 24*7hour avai...Satara Call girl escort *74796//13122* Call me punam call girls 24*7hour avai...
Satara Call girl escort *74796//13122* Call me punam call girls 24*7hour avai...
 
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
 
Mobile Application Development-Android and It’s Tools
Mobile Application Development-Android and It’s ToolsMobile Application Development-Android and It’s Tools
Mobile Application Development-Android and It’s Tools
 
Mobile Application Development-Components and Layouts
Mobile Application Development-Components and LayoutsMobile Application Development-Components and Layouts
Mobile Application Development-Components and Layouts
 
Android Application Components with Implementation & Examples
Android Application Components with Implementation & ExamplesAndroid Application Components with Implementation & Examples
Android Application Components with Implementation & Examples
 

Effective Android Data Binding

  • 1. Effective Android Data Binding Eric Maxwell
 Product Engineer @ Realm Android, iOS Developer
  • 2. What are you going to learn? • Introduction & Getting Started • Data Binding Features & Use Cases • Data Binding + MVVM • ViewModels and LiveData • Tips from using Data Binding in Production
  • 4. Data Binding Intro • Support Library to bind data directly to your views • Views can automatically update when underlying data changes • Views can connect directly to actions DATA TADA Data Data Data
  • 5. Enable Databinding android { ... dataBinding { enabled = true } } To enable data binding, add this to your app module build.gradle file…
  • 6. Traditional Layout <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android" ...> <EditText android:id="@+id/userNameEditText" /> <Button android:onClick=‘sayHiTo'/> </LinearLayout>
  • 7. Data binding Layout <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.acme.ViewModel" /> </data> <LinearLayout... <EditText android:id="@+id/userNameEditText" android:text="@{viewModel.initialUserName}" /> <Button android:text="@string/say_hi" android:onClick='@{() -> viewModel.sayHiTo(userNameEditText)}' /> </LinearLayout> </layout>
  • 8. Converting Existing Layouts <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android" ...> <EditText android:id="@+id/userNameEditText" /> <Button android:onClick=‘sayHiTo'/> </LinearLayout>
  • 9. Data binding Layout <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.acme.ViewModel" /> </data> <LinearLayout... <EditText android:id="@+id/userNameEditText" android:text="@{viewModel.initialUserName}" /> <Button android:text="@string/say_hi" android:onClick='@{() -> viewModel.sayHiTo(userNameEditText)}' /> </LinearLayout> </layout>
  • 10. Getting Started Example Covers • How to enable data binding • Building Layouts • Binding View Model • Data • Actions https://github.com/ericmaxwell2003/DataBindingDemos/
  • 11. Layout <?xml version="1.0" encoding="utf-8"?> <layout ...> <data> <variable name="viewModel" 
 type="com.acme.counter.CounterViewModel" /> </data> <android.support.constraint.ConstraintLayout ...> <TextView android:id="@+id/count" tools:text=“0" android:text="@{String.valueOf(viewModel.count)}" /> <Button android:id="@+id/decrement" android:onClick="@{() -> viewModel.decrement()}" /> <Button android:id="@+id/increment" android:onClick="@{() -> viewModel.increment()}" /> </android.support.constraint.ConstraintLayout> </layout> Invoke decrement() on click Invoke increment() on click Observing value of count property
  • 12. public class CounterViewModel extends BaseObservable { private int count = 0; public void increment() { setCount(getCount() + 1); } public void decrement() { setCount(getCount() - 1); } @Bindable public int getCount() { return count; } private void setCount(int count) { this.count = count; notifyPropertyChanged(BR.count); } } ViewModel @Bindable annotation makes this 
 property eligible for binding Triggers view observing count to update
  • 13. public class CounterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityCounterBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_counter); binding.setViewModel(new CounterViewModel()); } } Activity Generated from activity_counter.xml
  • 14. Activity public class CounterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityCounterBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_counter); binding.setViewModel(new CounterViewModel()); } } Connect ViewModel to View
  • 15. Activity public class CounterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityCounterBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_counter); binding.setViewModel(new CounterViewModel()); } } <data> <variable name="viewModel" type="com.acme.counter.CounterViewModel" /> </data> Layout variable declaration defines viewModel name
  • 16. public class CounterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityCounterBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_counter); binding.setViewModel(new CounterViewModel()); } } <data> <variable name="viewModel" type="com.acme.counter.CounterViewModel" /> </data> Activity Layout variable declaration defines viewModel type
  • 17. public class CounterViewModelTest { private CounterViewModel counterViewModel; @Before public void setup() { counterViewModel = new CounterViewModel(); } @Test public void testInitialState() { Assert.assertEquals(0, counterViewModel.getCount()); } @Test public void testIncrement() { for (int i = 1; i <= 5; i++) { counterViewModel.increment(); Assert.assertEquals(i, counterViewModel.getCount()); } } @Test public void testDecrement() { for (int i = 1; i <= 5; i++) { counterViewModel.decrement(); Assert.assertEquals(i * -1, counterViewModel.getCount()); } } } Testing data bound view models
  • 19. Inside <data> Tag <data> <!—- (Define 0..* variables) -—> <variable name="viewModel" type="com.acme.ViewModel" /> <!—- (Define 0..* imports) -—> <import type="android.view.View" /> </data> Variables • Define name and type of variable • Value set by hosting Activity/Fragment Imports • Define imports similar to imports for in classes • Import static -Utils classes, View, etc. • Example usages android:visibility="@{someCondition ? View.VISIBLE : View.GONE}" android:text='@{StringUtils.suffixWithAwesome("Everything is ")'
  • 26. ‘@{viewModel.value}' Where Value will resolve to (in order of precedence) • getValue() // Property Accessor • value() // Method with that name • value // Public property Expression Resolution
  • 27. ‘@{viewModel.value.subval}’ Expressions are Null Safe Expressions are null safe • viewModel == null // Okay!
  • 28. ‘@{viewModel.value.subval}’ Expressions are null safe • viewModel == null // Okay! • viewModel.value == null // I eat null for breakfast! Expressions are Null Safe
  • 29. ‘@{viewModel.value.subval}’ Expressions are null safe • viewModel == null // Okay! • viewModel.value == null // I eat null for breakfast! • viewModel.value.subval == null // NPE! Expressions are Null Safe
  • 30. ‘@{viewModel.value.subval}’ Expressions are null safe • viewModel == null // Okay! • viewModel.value == null // I eat null for breakfast! • viewModel.value.subval == null // … j/k you’re fine! Expressions are Null Safe
  • 31. ‘@{viewModel.value.subval ?? “Foo”}’ Expressions are null safe • viewModel == null // Okay! • viewModel.value == null // I eat null for breakfast! • viewModel.value.subval == null // … j/k you’re fine! Expressions are Null Safe
  • 33. public class CounterViewModel extends BaseObservable { private int count = 0; public void increment() { setCount(getCount() + 1); } public void decrement() { setCount(getCount() - 1); } @Bindable public int getCount() { return count; } private void setCount(int count) { this.count = count; notifyPropertyChanged(BR.count); } } Making Data Observable Base
 Observable Observable
 ViewModel <<Observable>> Counter
 ViewModel
  • 34. public class CounterViewModel extends BaseObservable { private int count = 0; public void increment() { setCount(getCount() + 1); // perform many other operations
 // update many data bound variables
 notifyChange(); } public void decrement() { setCount(getCount() - 1); } @Bindable public int getCount() { return count; } private void setCount(int count) { this.count = count; } } Making Data Observable Base
 Observable Observable
 ViewModel <<Observable>> Counter
 ViewModel
  • 35. public interface Observable { void addOnPropertyChangedCallback(...); void removeOnPropertyChangedCallback(...); public abstract static class OnPropertyChangedCallback { public abstract void onPropertyChanged(...); } } Making Data Observable Base
 Observable Observable
 ViewModel <<Observable>> Counter
 ViewModel
  • 36. /** * Bridge class which does everything android.databinding.BaseObservable does plus * extends Android Architecture Components ViewModel */ public abstract class ObservableViewModel extends ViewModel implements Observable { ... } How to make an Observable ViewModel Base
 Observable Observable
 ViewModel <<Observable>> Counter
 ViewModel
  • 37. public class CounterViewModel extends ObservableViewModel { private int count = 0; public void increment() { setCount(getCount() + 1); } public void decrement() { setCount(getCount() - 1); } @Bindable public int getCount() { return count; } private void setCount(int count) { this.count = count; notifyPropertyChanged(BR.count); } } Base
 Observable Observable
 ViewModel <<Observable>> Counter
 ViewModel How to make an Observable ViewModel
  • 38. public class CounterViewModel extends ViewModel { public ObservableInt count = new ObservableInt(0); public void increment() { count.set(count.get() + 1); } public void decrement() { count.set(count.get() - 1); } } Other Options
  • 39. Other Options Observable Primitives: ObservableInt, ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableFloat Observable Collections: ObservableArrayMap, ObservableArrayList public class CounterViewModel extends ViewModel { public ObservableInt count = new ObservableInt(0); public void increment() { count.set(count.get() + 1); } public void decrement() { count.set(count.get() - 1); } }
  • 40. Other Options Observable Primitives: ObservableInt, ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableFloat Observable Collections: ObservableArrayMap, ObservableArrayList public class CounterViewModel extends ViewModel { public ObservableInt count = new ObservableInt(0); public void increment() { count.set(count.get() + 1); } public void decrement() { count.set(count.get() - 1); } } Best used for simpler views 
 with only a few observable fields
  • 41. public class CounterViewModel extends ObservableViewModel { private int count = 0; public void increment() { AsyncTask.execute(new Runnable() { @Override public void run() { count++; notifyPropertyChanged(BR.countLabel); Log.i(TAG, "Counter [" + count + "], on bg thread"); } }); } ... } Update can happen in background threads You can change your data model in a background thread as long as it is not a collection. 
 
 https://developer.android.com/topic/libraries/data-binding/index.html#advanced_binding
  • 43. 1 Way Data Binding '@{expressions}'
  • 44. 2 Way Data Binding '@={expressions}'
  • 45. 2 Way Data Binding <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="vm" type="com.acme.loginform.LoginViewModel" /> </data> <LinearLayout...> ... <EditText android:id="@+id/userTextView" android:hint="@string/login_username_hint" tools:text=“user@acme.com” android:text=“@={vm.username}"/> <EditText android:id="@+id/passTextView" android:hint="@string/login_password_hint" tools:text="password" android:text=“@={vm.password}”/> <Button android:id="@+id/login_button" android:onClick="@{() -> vm.login()}" android:text="@string/connect_button_label" /> </LinearLayout> </layout> Username can be pre-populated from last username
  • 46. 2 Way Data Binding public class LoginViewModel extends ViewModel { public String username = sharedPrefs.getLastUsername(); public String password = ""; public void login() { updateLastUsedUsername(); loginService.attemptLogin(username, password, new FakeLoginService.LoginCallback() { ... } ); } private void updateLastUsedUsername() { sharedPrefs.setLastUsername(username); } }
  • 47. 2 Way Data Binding public class LoginViewModel extends ViewModel { public String username = sharedPrefs.getLastUsername(); public String password = ""; public void login() { updateLastUsedUsername(); loginService.attemptLogin(username, password, new FakeLoginService.LoginCallback() { ... } ); } private void updateLastUsedUsername() { sharedPrefs.setLastUsername(username); } }
  • 49. Binding Adapters @BindingAdapter({"app:imageUrl"}) public static void loadImage(ImageView view, String url) { Picasso.with(view.getContext()) .load(url) .into(view); } <ImageView android:id="@+id/imageView" tools:src="@drawable/design_time_sample_image" app:imageUrl=‘@{"http://acme.com/images/any.png"}' /> Binding Adapters allow you to add custom attributes to views
  • 50. Binding Adapters @BindingAdapter({"app:imageUrl"}) public static void loadImage(ImageView view, String url) { Picasso.with(view.getContext()) .load(url) .into(view); } <ImageView android:id="@+id/imageView" tools:src="@drawable/design_time_sample_image" app:imageUrl=‘@{"http://acme.com/images/any.png"}' /> Adds app:imageUrl attribute
  • 51. Binding Adapters @BindingAdapter({"app:imageUrl"}) public static void loadImage(ImageView view, String url) { Picasso.with(view.getContext()) .load(url) .into(view); } <ImageView android:id="@+id/imageView" tools:src="@drawable/design_time_sample_image" app:imageUrl=‘@{"http://acme.com/images/any.png"}' /> Applies to ImageView
  • 52. Binding Adapters @BindingAdapter({"app:imageUrl"}) public static void loadImage(View view, String url) { Picasso.with(view.getContext()) .load(url) .into((ImageView)view); } <ImageView android:id="@+id/imageView" tools:src="@drawable/design_time_sample_image" app:imageUrl=‘@{"http://acme.com/images/any.png"}' /> Applies to all Views
  • 53. Binding Adapters @BindingAdapter({"app:imageUrl"}) public static void loadImage(TextView view, String url) { } <ImageView android:id="@+id/imageView" tools:src="@drawable/design_time_sample_image" app:imageUrl=‘@{"http://acme.com/images/any.png"}' /> Binding Adapters allow you to add custom attributes to views Cannot find the setter for attribute 'app:imageUrl' with parameter type java.lang.String on android.widget.ImageView
  • 54. Binding Adapters @BindingAdapter({"app:imageUrl"}) public static void loadImage(ImageView view, String url) { Picasso.with(view.getContext()) .load(url) .into(view); } <ImageView android:id="@+id/imageView" tools:src="@drawable/design_time_sample_image" app:imageUrl=‘@{"http://acme.com/images/any.png"}' /> Value of expression 
 Must be an expression!
  • 55. @BindingAdapter({"app:imageUrl", "app:placeholder"}) public static void loadImage(ImageView view, String url, Drawable placeholder) { Picasso.with(view.getContext()) .load(url) .placeholder(placeholder) .into(view); } <ImageView android:id="@+id/imageView" tools:src="@drawable/design_time_sample_image" app:imageUrl='@{“http://acme.com/images/any.png"}' app:placeHolder="@{@drawable/ic_launcher_background}" /> Binding Adapters
  • 56. @BindingAdapter({"android:visibility"}) public static void visibility(final View view, final int newVisibility) { // Do something like animate or log visibility change,… view.setVisibility(newVisibility); } Override System Attribute Good example @ https://medium.com/google-developers/android-data-binding-animations-55f6b5956a64
  • 58. Converters <ImageView android:id="@+id/imageView" app:imageUrl='@{“http://acme.com/images/any.png"}' app:placeHolder="@{@drawable/ic_launcher_background}" android:visibility="@{viewModel.showImage}" /> @BindingConversion public static int convertBooleanToVisibility(boolean expression) { return expression ? View.VISIBLE : View.GONE; } …you can do this Applies for all attributes where boolean expression is given and int is required
  • 60. public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> { @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); MyRecyclerItemBinding itemBinding = MyRecyclerItemBinding.inflate(layoutInflater, parent, false); return new ViewHolder(itemBinding); } @Override public void onBindViewHolder(ViewHolder holder, int position) { ItemViewModel item = items.get(position); holder.bind(item); } class ViewHolder extends RecyclerView.ViewHolder { private MyRecyclerItemBinding itemBinding; ViewHolder(MyRecyclerItemBinding itemBinding) { super(itemBinding.getRoot()); this.itemBinding = itemBinding; } void bind(ItemViewModel viewModel) { itemBinding.setItemViewModel(viewModel); itemBinding.executePendingBindings(); } } Recycler View Adapter
  • 62. 2 Way Data Binding public class LoginViewModel extends ViewModel { ... public MutableLiveData<State> state = new MutableLiveData<>(); public MutableLiveData<String> error = new MutableLiveData<>(); public void login() { state.postValue(State.ATTEMPTING_LOGIN); updateLastUsedUsername(); loginService.attemptLogin(username, password, new FakeLoginService.LoginCallback() { @Override public void onSuccess(FakeLoginService.User user) { state.postValue(State.AUTHENTICATED); } @Override public void onFailure(Throwable e) { error.postValue("Login Error!!"); state.postValue(State.WAITING_USER); } }); } }
  • 63. 2 Way Data Binding public class LoginViewModel extends ViewModel { ... public MutableLiveData<State> state = new MutableLiveData<>(); public MutableLiveData<String> error = new MutableLiveData<>(); public void login() { state.postValue(State.ATTEMPTING_LOGIN); updateLastUsedUsername(); loginService.attemptLogin(username, password, new FakeLoginService.LoginCallback() { @Override public void onSuccess(FakeLoginService.User user) { state.postValue(State.AUTHENTICATED); } @Override public void onFailure(Throwable e) { error.postValue("Login Error!!"); state.postValue(State.WAITING_USER); } }); } }
  • 64. Observing Live Data viewModel.state.observe(this, new Observer<LoginViewModel.State>() { @Override public void onChanged(@Nullable LoginViewModel.State state) { if(state == null) { return; } switch (state) { case WAITING_USER: hideProgress(); break; case ATTEMPTING_LOGIN: showProgress(); break; case AUTHENTICATED: authSuccessful(); break; } } });
  • 65. Observing Live Data viewModel.error.observe(this, new Observer<String>() { @Override public void onChanged(@Nullable String errorMessage) { if(!TextUtils.isEmpty(errorMessage)) { showMessage(errorMessage); } } });
  • 67. Tips 1. Avoid adding logic to your expressions, put that in the ViewModel
 android:text='@{v.someCondition() ? @string/foo : @string/boo}' 
 android:onClick='@{() -> someCondition() ? doSomething() : void}'
  • 68. 1. Avoid adding logic to your expressions, put that in the ViewModel 2. Data Binding works really well with MVVM Tips
  • 69. 1. Avoid adding logic to your expressions, put that in the ViewModel 2. Data Binding works really well with MVVM 3. Make heavy use of tools attributes so you can see an example in your preview
 android:text=‘@{String.valueOf(vm.count)}’ 
 tools:text=‘0’ Tips
  • 70. 1. Avoid adding logic to your expressions, put that in the ViewModel 2. Data Binding works really well with ViewModels 3. Make heavy use of tools attributes so you can see an example in your preview 4. Custom attributes (BindingAdapters) can greatly simplify creating your views Tips
  • 71. 1. Avoid adding logic to your expressions, put that in the ViewModel 2. Data Binding works really well with ViewModels 3. Make heavy use of tools attributes so you can see an example in your preview 4. Custom attributes can greatly simplify creating your views, use them! 5. Run often in development cycles, it can be difficult to track down build time errors in your layouts •See tip #1 Tips
  • 72. Additional Materials • Other great talk on Data Binding • https://academy.realm.io/posts/droidkaigi-kevin-pelgrims-data-real-world-data-binding/ • https://academy.realm.io/posts/360-andev-2017-ben-mccanny-data-binding/ • https://academy.realm.io/posts/data-binding-android-boyar-mount/ • Great how to articles • https://medium.com/@georgemount007 • Samples • https://github.com/ericmaxwell2003/DataBindingDemos • https://github.com/ericmaxwell2003/ticTacToe/tree/mvvm