SE2016 Android Mikle Anokhin "Speed up application development with data binding"
Transcript of SE2016 Android Mikle Anokhin "Speed up application development with data binding"
Speed Up Application Development with Data Binding
Anokhin Mikle / Android Developer / MWDN Ltd.
First Part: Introduction
How to start (Java)
android { buildToolsVersion "24.0.1" dataBinding { enabled = true }}
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.1.2' }}
3
How to start (Kotlin)
apply plugin: 'kotlin-android'
android { buildToolsVersion "24.0.1" dataBinding { enabled = true } }
dependencies { kapt 'com.android.databinding:compiler:2.1.2'}
kapt { generateStubs = true}
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.1.2' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.3' }}
4
Binding (Model)
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data> <variable name="user" type="com.anokmik.databinding.model.User" /> </data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{user.firstName}"/>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
public class BindingModelFragment extends Fragment {
@Override public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(
R.layout.fragment_binding_model, container, false); FragmentBindingModelBinding binding
= FragmentBindingModelBinding.bind(view); binding.setUser(User.getDefault()); return view; }
}
5
Data Binding doesn’t handle view state
so you should specify id for such views as usual
Binding (Ids)
public class BindingIdsFragment extends Fragment { private TextView firstName; private TextView lastName;
@Override public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(
R.layout.fragment_binding_ids, container, false); FragmentBindingIdsBinding binding
= FragmentBindingIdsBinding.bind(view); firstName = binding.firstName; lastName = binding.lastName; return view; }
@Override public void onViewCreated(View view,
Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); User user = User.getDefault(); firstName.setText(user.firstName); lastName.setText(user.lastName); }
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<TextView android:id="@+id/first_name" android:layout_width="match_parent" android:layout_height="wrap_content"/>
<TextView android:id="@+id/last_name" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
7
Generated Binding
8
public class FragmentBindingModelBinding extends android.databinding.ViewDataBinding {
@Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } java.lang.String firstNameUser = null; java.lang.String lastNameUser = null; com.anokmik.databinding.model.User user = mUser; if ((dirtyFlags & 0x3L) != 0) { user = user; if (user != null) { firstNameUser = user.firstName; lastNameUser = user.lastName; } } if ((dirtyFlags & 0x3L) != 0) { this.mboundView1.setText(firstNameUser); this.mboundView2.setText(lastNameUser); } }
}
Bindings are generated at compile-time
Variables and Imports
<data>
<import type="android.graphics.drawable.Drawable" /> <import type="com.anokmik.databinding.model.User" /> <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/>
<variable name="user" type="User" /> <variable name="image" type="Drawable" /> <variable name="text" type="String" /> <variable name="array" type="String[]" /> <variable name="list" type="List<String>"/> <variable name="sparse" type="SparseArray<String>"/> <variable name="map" type="Map<String, String>"/> <variable name="index" type="int"/> <variable name="key" type="String"/>
</data>
<data>
<import type="android.app.Fragment"/> <import type="android.support.v4.app.Fragment" alias="SupportFragment"/>
</data>
10
Simple Data Model
public class User { private final String firstName; private final String lastName;
public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; } }
public class User { public final String firstName; public final String lastName;
public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
}
11
Observable Data Model
public class NotifyGreeting extends BaseObservable {
private String name;
@Bindable public String getName() { return name; }
public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); }
}
public class ObservableGreeting {
public ObservableString name = new ObservableString();
}
12
Include and Merge
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto">
<data> <variable name="user" type="com.example.User" /> </data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<include layout="@layout/name" bind:user="@{user}" /> </LinearLayout> </layout>
<layout xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.example.User"/> </data> <merge> <include layout="@layout/name" bind:user="@{user}"/> </merge> </layout>
13
Expressions
● mathematical +, -, /, *, %● string concatenation +● logical &&, ||● binary &, |, ^● unary +, -, !, ~● shift >>, >>>, <<● comparison ==, >, <, >=, <=● instanceof
● grouping ()● literals - character, String, numeric, null● cast● method calls● field access● array access []● ternary operator ?:
android:enabled="@{communicator.isLoginValid & communicator.isPasswordValid}"
14
Binding Providers
15
@BindingMethods({ @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"), @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"), @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps")})public class TextViewBindingAdapter {
@BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { ... }
}
public class Converters {
@BindingConversion public static String convertObservableToString(ObservableString observableString) { return observableString.get(); }
}
Data Binding Component
16
public class MainDataBindingComponent {
@BindingAdapter(value = {"defaultColor", "pressedColor"}) public void setButtonStateListBackground(Button button, int defaultColor, int pressedColor) { button.setBackground(getButtonStateListDrawable(defaultColor, pressedColor)); }
}
public class DataBindingComponentProvider implements DataBindingComponent {
@Override public MainDataBindingComponent getMainDataBindingComponent() { return new MainDataBindingComponent(); }
}
DataBindingUtil.setDefaultComponent(new DataBindingComponentProvider());
DataBindingUtil.setContentView(this, R.layout.activity_main, new DataBindingComponentProvider());
DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false, new DataBindingComponentProvider());
Two-way Binding
17
android:text="@={communicator.editTextValue}"
AdapterView android:selectedItemPosition
CalendarView android:date
CompoundButton android:checked
DatePicker android:year / android:month / android:day
NumberPicker android:value
RadioGroup android:checkedButton
RatingBar android:rating
SeekBar android:progress
TabHost android:currentTab
TextView android:text
TimePicker android:hour / android:minute
app:color="@={communicator.color}"
Two-way binding requires getters and setters for fields
Second Part: Typical Use Cases
User Flow
20
Login Profile
User Layout Custom Items
21
Toolbar
<android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ToolbarTheme" app:onNavigationClick="@{navigationClickListener}" app:popupTheme="@style/PopupTheme" />
Support Toolbar Component
22
@BindingMethods({ @BindingMethod(type = Toolbar.class, attribute = "onMenuItemClick", method = "setOnMenuItemClickListener"), @BindingMethod(type = Toolbar.class, attribute = "onNavigationClick", method = "setNavigationOnClickListener")})public final class SupportToolbarComponent {
}
@BindingMethods( BindingMethod(type = Toolbar::class, attribute = "onMenuItemClick", method = "setOnMenuItemClickListener"), BindingMethod(type = Toolbar::class, attribute = "onNavigationClick", method = "setNavigationOnClickListener"))class SupportToolbarComponent
User Layout Custom Items
23
Text Input Layout Button
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.login()}" android:text="@string/login" />
<android.support.design.widget.TextInputLayout style="@style/TextInputLayoutStyle" android:layout_width="match_parent" android:layout_height="wrap_content" app:editable="@{presenter.isEditing}" app:error="@{@string/error_name_not_valid}" app:showError="@{!presenter.firstNameValid}">
<android.support.design.widget.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_first_name" android:text="@={presenter.observableUser.firstName}" />
</android.support.design.widget.TextInputLayout>
Text Input Layout Component
24
public final class TextInputLayoutComponent {
@BindingAdapter({"error", "showError"}) public void setError( TextInputLayout view, String error, boolean showError ) { view.setError(showError ? error : null); }
@BindingAdapter("editable") public void setEditable( TextInputLayout view, boolean isEditable ) { view.setFocusable(isEditable); EditText editText = view.getEditText(); if (editText != null) { editText.setCursorVisible(isEditable); editText.setFocusable(isEditable); editText.setFocusableInTouchMode(isEditable); Drawable background = editText.getBackground(); if (background != null) { background.setAlpha(isEditable ? 255 : 0); } if (isEditable) { Editable editableText = editText.getText(); int selectionStart = editText.getSelectionStart(); int selectionEnd = editText.getSelectionEnd(); if (!TextUtils.isEmpty(editableText) && (selectionStart == 0) && (selectionEnd == 0)) { editText.setSelection(editableText.length()); } } } }
}
class TextInputLayoutComponent {
@BindingAdapter("error", "showError") fun setError( view: TextInputLayout, error: String, showError: Boolean ) { view.error = if (showError) error else null }
@BindingAdapter("editable") fun setEditable( view: TextInputLayout, isEditable: Boolean ) { view.isFocusable = isEditable view.editText?.apply { isCursorVisible = isEditable isFocusable = isEditable isFocusableInTouchMode = isEditable background?.apply { alpha = if (isEditable) 255 else 0 } if (isEditable) { val editableText = text if (!TextUtils.isEmpty(editableText) && (selectionStart == 0) && (selectionEnd == 0)) { setSelection(editableText.length) } } } }
}
Trips Flow
25
Trips List
Trip Event
Trip Details
Trips Layout Custom Items
26
Recycler View
<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:padding="@dimen/fragment_padding" app:decoration="@{decoration}" app:items="@{presenter.trips}" app:layoutManager="@{layoutManager}" app:viewHolderPresenter="@{presenter.viewHolderPresenter}" />
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@{trip.title}" android:textColor="@color/material_dark_primary_text_color" android:textSize="@dimen/title_text_size" android:textStyle="bold" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{DateUtils.format(trip.startDate, trip.finishDate)}" android:textColor="@color/material_dark_secondary_text_color" android:textSize="@dimen/caption_text_size" android:textStyle="bold" />
TextView (Title) TextView (Dates)
Recycler View Component
27
public final class RecyclerViewComponent {
@BindingAdapter({"items", "viewHolderPresenter"}) public void setContent( final RecyclerView view, List<?> list, ViewHolderPresenter<?> viewHolderPresenter ) { BinderRecyclerViewAdapter adapter = new BinderRecyclerViewAdapter( LayoutInflater.from(view.getContext()), list, viewHolderPresenter ); view.setAdapter(adapter); if (list instanceof ObservableList) { view.addOnAttachStateChangeListener( new ObservableListAttachStateChangeListener( (ObservableList) list, new RecyclerViewListChangedCallback(adapter) ) ); } }
@BindingAdapter("decoration") public void setDecoration(RecyclerView view, RecyclerView.ItemDecoration decoration) { view.addItemDecoration(decoration); }
@BindingAdapter("decoration") public void setDecoration(RecyclerView view, RecyclerView.ItemDecoration[] decorations) { for (RecyclerView.ItemDecoration decoration : decorations) { view.addItemDecoration(decoration); } }
}
class RecyclerViewComponent {
@BindingAdapter("items", "viewHolderPresenter") fun <T> setContent( view: RecyclerView, list: List<T>, viewHolderPresenter: ViewHolderPresenter<T> ) { val adapter = BinderRecyclerViewAdapter<T, ViewDataBinding>( LayoutInflater.from(view.context), list, viewHolderPresenter ) view.adapter = adapter if (list is ObservableList<T>) { view.addOnAttachStateChangeListener( ObservableListAttachStateChangeListener( list, RecyclerViewListChangedCallback(adapter) ) ) } }
@BindingAdapter("decoration") fun setDecoration(view: RecyclerView, decoration: RecyclerView.ItemDecoration) { view.addItemDecoration(decoration) }
@BindingAdapter("decoration") fun setDecoration(view: RecyclerView, decorations: Array<RecyclerView.ItemDecoration>) { for (decoration in decorations) { view.addItemDecoration(decoration) } }
}
Trips Layout Custom Items
28
View Pager
<android.support.v4.view.ViewPager android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:items="@{presenter.photoAttachments}" app:viewHolderPresenter="@{presenter.viewHolderPresenter}" />
View Pager Component
29
class ViewPagerComponent {
@BindingAdapter("items", "viewHolderPresenter") fun <T> setContent(view: ViewPager, list: List<T>, viewHolderPresenter: ViewHolderPresenter<T>) { val adapter = BinderViewPagerAdapter( LayoutInflater.from(view.context), list, viewHolderPresenter ) view.adapter = adapter if (list is ObservableList<T>) { view.addOnAttachStateChangeListener( ObservableListAttachStateChangeListener( list, ViewPagerListChangedCallback(adapter) ) ) } }
}
public final class ViewPagerComponent {
@BindingAdapter({"items", "viewHolderPresenter"}) public void setContent(ViewPager view, List<?> list, ViewHolderPresenter<?> viewHolderPresenter) { BinderViewPagerAdapter adapter = new BinderViewPagerAdapter( LayoutInflater.from(view.getContext()), list, viewHolderPresenter ); view.setAdapter(adapter); if (list instanceof ObservableList) { view.addOnAttachStateChangeListener(new ObservableListAttachStateChangeListener( (ObservableList) list, new ViewPagerListChangedCallback(adapter)) ); } }
}
Image View Component
30
class ImageViewComponent {
@BindingAdapter("android:src") fun loadImage(view: ImageView, imageUrl: String) { Picasso.with(view.context).load(imageUrl).into(view) }
}
public final class ImageViewComponent {
@BindingAdapter("android:src") public void loadImage(ImageView view, String imageUrl) { Picasso.with(view.getContext()).load(imageUrl).into(view); }
}
Thanks for your attention!
sources:
first part: https://github.com/anokmik/data-binding
second part: https://github.com/anokmik/trip-assistant
mail: [email protected]
skype: anokmik
References
● Data Binding Guide from Google (https://goo.gl/1yFPtt)
● No More findViewById (https://goo.gl/ugV86t)
● Android Data Binding: Adding Some Variability (https://goo.gl/aDTMmp)
● Android Data Binding: Express Yourself (https://goo.gl/NZ15Tz)
● Android Data Binding: Custom Setters (http://goo.gl/DnKgON)
● Android Data Binding: The Big Event (https://goo.gl/qcpdpU)
● Android Data Binding: That <include> Thing (https://goo.gl/QiqpQT)
● Android Data Binding: Let’s Flip This Thing (https://goo.gl/Gok1gv)
● Descent into Data Binding (https://goo.gl/1PTE7F)
● Two-way Data Binding (https://goo.gl/DQtRrh | https://goo.gl/KjY2Ze)
● Porting to Data Binding (https://goo.gl/7CA7vX)
● Advanced Data Binding (https://www.youtube.com/watch?v=DAmMN7m3wLU)
● Data Binding Techniques (https://www.youtube.com/watch?v=WdUbXWztKNY)
32
Q&A