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

Two-Way Data Binding by Michel Marin

GDG Montreal
October 25, 2017
38

Two-Way Data Binding by Michel Marin

GDG Montreal

October 25, 2017
Tweet

More Decks by GDG Montreal

Transcript

  1. Bio • Michel Marin, PMP • IMS Manager Bureau du

    développement et des relations avec les diplômés Université de Montréal • eCaptain for the Ellucian Advance Tech Suite • PADI Master Diver
  2. Quick Demo My Air Buddy and I • SDK 24

    • SQLite • RecyclerView • Data Binding • Parcelable • No MVP, no MVC, no MVI, version 2.0
  3. Goals To share my pain Really, to share what I’ve

    discovered, tricks of the trade and the likes
  4. Android Data Binding Array of POJO POJO POJO POJO POJO

    Data Binding For () { pojo.setMyVariable() } pojo.setMyVariable() Database independent
  5. Basic Setup In build.gradle (Project): buildscript { repositories { jcenter()

    } dependencies { classpath 'com.android.tools.build:gradle:1.5.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }
  6. Basic setup <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="cylinderType"

    type="ca.iprojet.myairbuddyandi.CylinderType"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> Start your new layout here… </LinearLayout> </layout> POJO / Data Object POJO alias’ through this layout Case sensitive through this layout Within same single layout
  7. Basic setup Cont’d <data> <variable name="cylinderType" type="ca.iprojet.myairbuddyandi.CylinderType"/> </data> <LinearLayout android:orientation="vertical"

    android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:text="@={cylinderType.cylinderType}" android:id="@+id/editTextCT" CylinderType.getCylinderType() CylinderType.setCylinderType() 1 way: “@{}“ 2 way: “@ ={}“ Not required for Data Binding But very useful to avoid findViewById() Use Text Input Layout And error message Will generate class “CylinderTypeActivityBinding “
  8. POJO – Data Object • Observable • Parcelable • Works

    with Data Binding • Works with RecyclerView public class Diver extends BaseObservable implements Parcelable { }
  9. Views and Listeners cont’d • CheckBox android:visibility="@{segmentType.visible == View.VISIBLE ?

    View.VISIBLE : segmentType.visible == View.INVISIBLE ? View.INVISIBLE : View.GONE}" /> android:checked="@={cylinderPick.checked}« android:visibility="@{cylinderPick.visible ? View.VISIBLE : View.GONE}"/>
  10. Views and Listeners Cont’d • Radio Button (Multiple) <TableRow android:id="@+id/TableRow01"

    android:layout_width="match_parent" android:layout_height="wrap_content" android:weightSum="7" android:stretchColumns="*" android:orientation="horizontal"> <RadioButton android:id="@+id/RadioButton" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content"/>
  11. Views and Listeners Cont’d • Radio Button (Multiple) Cont’d mBinding

    = DataBindingUtil.setContentView(this,R.layout.sac_rmv_activity); mSacRmvList = mAirDa.getAllSacRmv(); mSacRmvAdapter = new SacRmvAdapter(mSacRmvList); mBinding.recycler.setAdapter(mSacRmvAdapter);
  12. Views and Listeners Cont’d • Radio Button (Multiple) Cont’d @Override

    public void onBindViewHolder(ViewHolder holder, int position) { SacRmv dataObject = mSacRmvList.get(position); SacRmvBinding binding; binding = DataBindingUtil.bind(holder.itemView); holder.getBinding().setVariable(BR.sacrmv, dataObject); holder.getBinding().executePendingBindings(); // Checks the initial RadioButton if (dataObject.getDiveTypeSelected().equals("Y")) { RadioButton checkedRb = binding.RadioButton; checkedRb.setChecked(true); mLastCheckedRb = checkedRb; } holder.bindSacRmv(dataObject); mSacRmv = dataObject; } We know what POJO has the initial Radio Button @ ON
  13. Views and Listeners Cont’d • Radio Button (Multiple) Cont’d class

    ViewHolder extends RecyclerView.ViewHolder { private SacRmvBinding binding; private ViewHolder(View itemView) { super(itemView); binding = DataBindingUtil.bind(itemView); RadioButton radioButton = binding.RadioButton; radioButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mLastCheckedRb != null) { mLastCheckedRb.setChecked(false); } mLastCheckedRb = (RadioButton) view; int position = getAdapterPosition(); mSacRmv = mSacRmvList.get(position); } }); } Set the previous Radio Button @ ON to OFF The new Radio Button becomes the previous Radio Button @ ON POJO to be updated in the Database with the Radio Button @ ON
  14. Views and Listeners Cont’d • Radio Button (Two - On

    & Off) <RadioGroup android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" android:orientation="horizontal" app:setOnCheckedChangeListener="@{dive.getOnRadioGroupChanged}"> <TextView android:id="@+id/salinityH" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/blue" android:textSize="18sp" android:text="@string/hint_salinity"/> 1 Way Data Binding Used to set mHasMyDataChanged Other solution would be to keep value before and after
  15. Views and Listeners Cont’d • Radio Button (Two - On

    & Off) Cont’d <RadioButton android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_weight="0.5" android:id="@+id/salinityS" android:text="@string/radio_button_salt" android:checked="@={dive.salinity}"/> <RadioButton android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_weight="0.5" android:id="@+id/salinityF" android:text="@string/radio_button_fresh" android:checked="@{dive.fresh}"/> </RadioGroup> 2 Way Data Binding Salinity is read from the POJO and set to the POJO 1 Way Data Binding
  16. Views and Listeners Cont’d • Radio Button (Two - On

    & Off) Cont’d public class MyRadioGroupWatcher implements RadioGroup.OnCheckedChangeListener { public static final String LOG_TAG = "MyRadioGroupWatcher"; @Override public void onCheckedChanged(RadioGroup group, int checkedId) { // TODO Auto-generated method stub } }
  17. Views and Listeners Cont’d • Radio Button (Two - On

    & Off) Cont’d @Bindable public RadioGroup.OnCheckedChangeListener getOnRadioGroupChanged() { return new MyRadioGroupWatcher() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { super.onCheckedChanged(group, checkedId); mHasDataChanged = true; } }; }
  18. Views and Listeners Cont’d • Radio Button (Two - On

    & Off) Cont’d • 2 Way Data Binding public boolean getSalinity() {return mSalinity;} public void setSalinity(boolean salinity) {mSalinity = salinity;} • 1 Way Data Binding (returning the inverted value of Salinity) public boolean getFresh() {return !mSalinity;} 4 • Note: No setFresh() needed because it is using 1 Way Data Binding!
  19. Views and Listeners Cont’d • Radio Button (Two - On

    & Off) Cont’d • SELECT in SQLite • UPDATE in SQLite values.put(AirDBHelper.TABLE_DIVE_SALINITY, (dive.getSalinity()) ? 1 : 0); dive.setSalinity((cursor.getInt(cursor.getColumnIndex(AirDBHelper.TABLE_DIVE_SALINITY)) == MyConstants.oneI)? true : false);
  20. Views and Listeners • Spinner <fr.ganfra.materialspinner.MaterialSpinner android:id="@+id/spinnerStatus" android:layout_width="match_parent" android:layout_height="wrap_content" android:selectedItemPosition="@={dive.statusPosition}"

    app:ms_floatingLabelColor="@color/blue" app:ms_multiline="true" app:ms_enableFloatingLabel="true" app:ms_enableErrorLabel="false" app:ms_floatingLabelText="@string/hint_status" app:ms_hint="@string/hint_status" app:ms_baseColor="@color/light_blue" app:setOnItemSelectedListener="@{dive.getOnSpinnerChangedStatus}" app:adapter="@{dive.AdapterStatus}" /> 2 Way Data Binding To load the Spinner Adapter 1 Way Data Binding To set the listener 1 Way Data Binding
  21. Views and Listeners • Spinner Cont’d public int getStatusPosition() {return

    mStatusPosition;} public void setStatusPosition(int statusPosition) {mStatusPosition = statusPosition;} Must turn the value into a position Must turn the position into a value
  22. Views and Listeners • Spinner Cont’d public class MySpinnerWatcher implements

    AdapterView.OnItemSelectedListener { public static final String LOG_TAG = "MySpinnerWatcher"; @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub } @Override public void onNothingSelected(AdapterView<?> arg0) { // TODO Auto-generated method stub } }
  23. Views and Listeners • Spinner Cont’d @Bindable public AdapterView.OnItemSelectedListener getOnSpinnerChangedStatus()

    { return new MySpinnerWatcher() { @Override public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { super.onItemSelected(parent, v, position, id); if (position >= MyConstants.zeroi && position + HINT_OFFSET != mStatusPosition){ mHasDataChanged = true; mStatus = String.valueOf(parent.getAdapter().getItem(position + HINT_OFFSET)); } } }; }
  24. Activity - List private DivePickActivityBinding mBinding = null; @Override protected

    void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.dive_pick_activity); mBinding.fabDive.setOnClickListener(new View.OnClickListener() { }); mDivePickList = mAirDa.getAllDivesWBuddy(); mDivePickAdapter.setDiveList(mDivePickList); private DivePickAdapter mDivePickAdapter; mBinding.recycler.setAdapter(mDivePickAdapter); import ca.iprojet.myairbuddyandi.databinding.DivePickActivityBinding;
  25. RecyclerView Adapter binding.hdrLogBookNo.setOnClickListener(new View.OnClickListener() { }); binding = DataBindingUtil.bind(itemView); checkBox

    = binding.checkBox; letterCircle = binding.gmailitemLetter; expandedArea = binding.expendArea; import ca.iprojet.myairbuddyandi.databinding.DivePickBinding; import ca.iprojet.myairbuddyandi.databinding.DivePickHeaderBinding; private CheckBox checkBox; private ImageView letterCircle; private DiverPickBinding binding; private TableLayout expandedArea;
  26. Activity - Detail import ca.iprojet.myairbuddyandi.databinding.DiveActivityBinding; protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.dive_activity); private DiveActivityBinding mBinding = null; mAirDa.getDiveGroup(mDive.getDiveNo(),mDive); mBinding.setDive(mDive); mBinding.cancelButton.setOnClickListener(new View.OnClickListener() { });
  27. Initialization • Initialization of views must be done through the

    model • Otherwise the listener kicks in • And your POJO thinks your data has been modified • Do not use findViewById • Do not use mBinding.editTextXX if (mDive.getDiveNo() == MyConstants.zeroL) { // Set the default values mDive.setLogBookNo(mAirDa.getLastLogBookNo() + 1); mDive.setDate(MyFunctions.getTodaysDate()); mDive.setStatus(getString(R.string.default_status)); // Plan
  28. Validation and Text Input Layout • Supports Text Input Layout,

    with Hint and Error Message if (!mBinding.editTextE.getText().toString().trim().isEmpty() && !isValidEmail(mDiver.getEmail())) { mBinding.editTextE.setError(getString(R.string.msg_email)); requestFocus(mBinding.editTextE); return false; } else { return true; }
  29. Validation and Text Input Layout • Or the data is

    already in the POJO / Data Object if (!isValidLogBookNo(mDive.getLogBookNo())) { mBinding.editTextLBN.setError(getString(R.string.msg_log_book_no)); requestFocus(mBinding.editTextLBN); return false; } else { return true; }
  30. Validation and Text Input Layout • To change the data

    on the layout mBinding.myBuddyLbl.setText(diver.getFullName());
  31. Validation and Text Input Layout • Has my data changed?

    • In order to display warning message “Are you sure you want to cancel?” • Must used Reusable Listener • TextWatcher • SpinnerWatcher • RadioGroupWatcher • Others to develop!
  32. Compile errors • A single error invalidates the whole Data

    Binding Library • Don’t panic, just go to the last error! • Fix the alias, the setter, the getter or "`` + "
  33. Compile errors • Some times has to drilldown to the

    Data Binding Library itself! • Don’t change anything!!!
  34. Constraint • RecyclerView Updatable with 2 Way Data Binding •

    Could not find a way to use the mBinding.editTextXX and Text Input Layout • Workaround is to work with the ArrayList<POJO> since the data is already in the POJOs • And use Toast to display error message
  35. Compile errors Cont’d • Same with BR.cylinderType • Same with

    import ca.iprojet.myairbuddyandi.databinding. CylinderActivityBinding; • Same with private DiveActivityBinding mBinding = null; Generated Bindable Library containing all aliases on the layouts
  36. Pros & Cons Pros • No boiler plates • Becomes

    fast, easy and fun Cons • Multiple Radio Buttons • RecyclerView in Edit mode
  37. Credits • Samuel Dionne • Layouts, Icons and Tricks! •

    Eloïse Marin • Creating the Dive Graphics • Nelia Fernández Flores • Brushing up the PowerPoint presentation • Brushing up the Dive Graphics