Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Boil Boilerplate Code with Data Binding

Boil Boilerplate Code with Data Binding

Learn Data Binding and ways it can make Android Development easier. Presentation at GDG Mumbai, DevFest 2017.

Chetan Sachdeva

October 28, 2017
Tweet

More Decks by Chetan Sachdeva

Other Decks in Programming

Transcript

  1. – Dead Poet’s Society “No matter what anybody tells you

    words and ideas can change the world.”
  2. What is a Language? 1. First we listen, then speak

    and then we write. 2. We learn from structures. 3. Every word has a context. 4. Vocabulary and knowledge makes you express better. 5. The more we read, the more we learn the language.
  3. What is a Computer Language? 1. First we listen, then

    speak and then we write. 2. We learn from structures. 3. Every word has a context. 4. Vocabulary and knowledge makes you express better. 5. The more we read, the more we learn the language.
  4. What? 1. Software Pattern to simplify UI development. 2. Binds

    UI elements to an App Domain Model. 3. Minimizes the glue code. 4. Incorporates Observer pattern. 5. Flexibility and broad compatibility (API level 7+). 6. Short and readable code.
  5. Why? private TextView name; private TextView age; protected void onCreate(Bundle

    savedInstanceState) { setContentView(R.layout.activity_main); name = (TextView) findViewById(R.id.name); age = (TextView) findViewById(R.id.age); } public void updateUI(User user) { if (user == null) { name.setText(null); age.setText(null); } else { name.setText(user.getName()); mLastName.setText(user.getLastName()); } } INSIPID BOILERPLATE BLOATED CLASSES REDUNDANT
  6. Why? private @BindView(R.id.name) TextView name; private @BindView(R.id.age) TextView age; protected

    void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); ButterKnife.bind(this); } public void updateUI(User user) { if (user == null) { name.setText(null); age.setText(null); } else { name.setText(user.getName()); mLastName.setText(user.getLastName()); } } Use Holdr? Use Butterknife?
  7. Data Bound Code private ActivityMainBinding binding; protected void onCreate(Bundle savedInstanceState)

    { binding = DataBindingUtil.setContentView(this, R.layout.activity_main); } public void updateUI(User user) { binding.setUser(user); } <layout> <data> <variable name="user" type=“com.android.example.UserModel"/> </data> <LinearLayout …> <TextView android:text="@{user.name}"/> <TextView android:text="@{String.valueOf(user.age)}"/> </LinearLayout> </layout> Moustache Operator Generated Class View Model Layout Tag
  8. How? android { .... dataBinding { enabled = true }

    } In your app-level build.gradle // For Activity binding = DataBindingUtil.setContentView(this, R.layout.layout); // For Fragment binding = DataBindingUtil.inflate(inflater, R.layout.layout, container, false); // For ViewHolder binding = DataBindingUtil.bind(view); In your View
  9. public class LoginModel extends BaseObservable { private String email; private

    String password; @Bindable public String getEmail() { return email; } public void setEmail(String email) { this.email = email; notifyPropertyChanged(BR.email); } @Bindable public String getPassword() { return password; } public void setPassword(String password) { this.password = password; notifyPropertyChanged(BR.password); } } View Model Java
  10. View Model Kotlin public class LoginModel : BaseObservable() { @get:Bindable

    var email: String? = null set(email) { field = email notifyPropertyChanged(BR.email) } @get:Bindable var password: String? = null set(password) { field = password notifyPropertyChanged(BR.password) } }
  11. public class LoginModel { public final ObservableField<String> email = new

    ObservableField<>(); public final ObservableField<String> password = new ObservableField<>(); public String getEmail() { return email.get(); } public String getPassword() { return password.get(); } public void setEmail(String email) { this.email.set(email); } public void setPassword(String password) { this.password.set(password); } } ObservableField<>
  12. public class LoginModel implements Observable { public String email; public

    String password; private PropertyChangeRegistry registry = new PropertyChangeRegistry(); @Bindable public String getEmail() { return email; } public void setEmail(String email) { this.email = email; registry.notifyChange(this, BR.email); setLoginEnabled(isEmailAndPasswordSet()); } … @Override public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) { registry.add(callback); } @Override public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) { registry.remove(callback); } } PropertyChangeRegistry
  13. Event Handling <layout> <data> <variable name="model" type="com.android.example.LoginModel"/> <variable name="handler" type="com.android.example.MainHandler"/>

    </data> <LinearLayout …> <EditText android:text="@{model.email}"/> <EditText android:text="@{model.password}"/> <Button android:onClick="@{ () -> handler.onLoginClicked(model.email, model.password)}”/> </LinearLayout> </layout>
  14. Event Handling public class MainActivity extends AppCompatActivity implements MainHandler {

    private ActivityMainBinding binding; private LoginModel model = new LoginModel(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.setModel(model); binding.setHandler(this); } @Override public void onLoginClicked(String email, String password) { Toast.makeText(this, "Email: " + email + "\nPassword: " + password, Toast.LENGTH_SHORT).show(); } }
  15. Event Handling public class Presenter { public void onCompletedChanged(Task task,

    boolean completed){} } <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:onCheckedChanged="@{ (cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
  16. Two Way binding <layout> <data> <variable name="model" type="com.android.example.LoginModel"/> <variable name="handler"

    type="com.android.example.MainHandler"/> </data> <LinearLayout …> <EditText android:text="@={model.email}"/> <EditText android:text="@={model.password}"/> <Button android:onClick="@{ () -> handler.onLoginClicked(model.email, model.password)}” android:enabled=“@{model.loginEnabled}"/> </LinearLayout> </layout> 2-way binding (Moustache with a nose)
  17. public class LoginModel extends BaseObservable { private String email; private

    String password; private boolean loginEnabled; @Bindable public String getEmail() { return email; } public void setEmail(String email) { this.email = email; notifyPropertyChanged(BR.email); setLoginEnabled(isEmailAndPasswordSet()); } … private boolean isEmailAndPasswordSet() { return !TextUtils.isEmpty(getEmail()) && !TextUtils.isEmpty(getPassword()); } } Validations Good bye Text watchers "
  18. Resources In Expressions: android:padding="@{isBig ? @dimen/bigPadding : @dimen/smallPadding}" Inline string

    formatting: android:text="@{@string/nameFormat(firstName, lastName)}" Inline plurals: android:text="@{@plurals/banana(bananaCount)}"
  19. Null Safety <layout> <data> <variable name="user" type=“com.android.example.UserModel"/> </data> <LinearLayout …>

    <TextView android:text=“@{user.card.number}”/> <TextView android:text=“@{user.card.from}”/> </LinearLayout> </layout> UI won’t crash if user is null. @{user.card.number, default=@string/no_number} @{user.card.number ?? @string/no_number} Use Null Coalescing
  20. RecyclerView Binding public class UserViewHolder extends RecyclerView.ViewHolder { static UserViewHolder

    create(LayoutInflater inflater, ViewGroup parent) { UserItemBinding binding = UserItemBinding.inflate(inflater, parent, false); return new UserViewHolder(binding); } private UserItemBinding mBinding; private UserViewHolder(UserItemBinding binding) { super(binding.getRoot()); mBinding = binding; } public void bindTo(User user) { mBinding.setUser(user); mBinding.executePendingBindings(); } }
  21. RecyclerView Binding public UserViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { return

    UserViewHolder.create(mLayoutInflater, viewGroup); } public void onBindViewHolder(UserViewHolder userViewHolder, int position) { userViewHolder.bindTo(mUserList.get(position)); }
  22. Custom View Inflation public class CustomView extends LinearLayout implements CustomViewHandler

    { private LayoutCustomViewBinding binding; private CustomViewModel model = new CustomViewModel() public CustomView(Context context) { this(context, null); } public CustomView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } @Override public void init(Context context) { final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); binding = LayoutCustomViewBinding.inflate(inflater, this, true); binding.setModel(model); binding.setHandler(this); } @Override public void onCustomViewClicked() { Toast.makeText(this, “Hello", Toast.LENGTH_SHORT).show(); } }
  23. Binding Adapter @BindingAdapter({"app:src", "app:error"}) public static void loadImage(ImageView view, String

    srcUrl, Drawable error) { Glide.with(view.getContext()) .load(srcUrl) .error(error) .into(view); } <layout> <data> <variable name="user" type="com.android.example.UserModel"/> </data> <LinearLayout …> <ImageView app:src=“@{user.imageUrl}” app:error=“@{@drawable/ic_error}”/> </LinearLayout> </layout> @BindingAdapter(value = {“app:src", "app:error"}, requireAll = false) Has to be static
  24. Binding Conversions @BindingConversion public static int convertBooleanToVisibility(boolean visible) { return

    visible ? View.VISIBLE : View.GONE; } <TextView android:visible=“@{user.age < 18}” /> Great use case for Empty/Placeholder Views <data> <import type="android.view.View"/> … </data> <TextView android:visible="@{user.age < 18 ? View.GONE : View.VISIBLE}" <TextView android:visible=“@{user.isAdult}” />
  25. Binding Fonts // In Application class FontCache fontCache = FontCache.getInstance(this);

    fontCache.addFont("droid", "DroidSerif.ttf"); // In BindingAdapter @BindingAdapter({"app:font"}) public static void setFont(TextView textView, String fontName) { textView .setTypeface(FontCache.getInstance(context) .get(fontName)); } // In Layout <TextView app:font="@{'droid'}" />
  26. Dependency Injection public interface TestableAdapter { @BindingAdapter("android:src") void setImageUrl(ImageView imageView,

    String url); } public interface DataBindingComponent { TestableAdapter getTestableAdapter(); } DataBindingUtil.setDefaultComponent(myComponent); ‐ or ‐ binding = MyLayoutBinding.inflate(layoutInflater, myComponent); In Activity In Application
  27. Daggerification // Create BindingComponent @Singleton public class BindingComponent implements DataBindingComponent

    { BindingAdapter bindingAdapter; @Inject public BindingComponent() { } public BindingAdapter getBindingAdapter() { return bindingAdapter; } } // Create getter of BindingComponent AppComponent @Singleton @Component(modules = {AppModule.class}) public interface AppComponent { void inject(MyApplication myApplication); BindingComponent getBindingComponent(); } // Set DefaultComponent in Application class DataBindingUtil.setDefaultComponent(component.getBindingComponent());
  28. Rxification compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0' Button b = (Button)findViewById(R.id.button); Subscription buttonSub =

    RxView.clicks(b).subscribe(new Action1<Void>() { @Override public void call(Void aVoid) { // do some work here } }); RxBinding is a set of libraries that allow you to react to user interface events via the RxJava paradigm.
  29. Search using RxBinding RxTextView.textChanges(searchTextView) .filter(new Func1<String, Boolean> (){ @Override public

    Boolean call(String s) { return s.length() > 2; } }) .debounce(100, TimeUnit.MILLISECONDS) .switchMap(new Func1<String, Observable<List<Result>>>() { makeApiCall(s); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* attach observer */);