Share
Embed
Transcript
49 slides
-
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 -
Quick Demo My Air Buddy and I • SDK 24
• SQLite • RecyclerView • Data Binding • Parcelable • No MVP, no MVC, no MVI, version 2.0 -
How many findViewById in this app? • Under 10? •
Under 100? • Over 100? -
Goals To share my pain Really, to share what I’ve
discovered, tricks of the trade and the likes -
Android Data Binding Array of POJO POJO POJO POJO POJO
Data Binding For () { pojo.setMyVariable() } pojo.setMyVariable() Database independent -
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 } } -
Basic Setup Cont’d In build.gradle (Module): android { … dataBinding
{ enabled = true } } -
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 -
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 “ -
POJO – Data Object • Observable • Parcelable • Works
with Data Binding • Works with RecyclerView public class Diver extends BaseObservable implements Parcelable { } -
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}"/> -
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"/> -
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); -
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 -
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 -
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 -
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 -
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 } } -
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; } }; } -
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! -
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); -
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 -
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 -
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 } } -
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)); } } }; } -
Views and Listeners • Spinner Cont’d public ArrayAdapter<String> getAdapterStatus ()
{return mAdapterStatus;} -
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; -
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; -
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() { }); -
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 -
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; } -
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; } -
Validation and Text Input Layout • To change the data
on the layout mBinding.myBuddyLbl.setText(diver.getFullName()); -
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! -
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 "`` + " -
Compile errors • Some times has to drilldown to the
Data Binding Library itself! • Don’t change anything!!! -
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 -
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 -
Pros & Cons Pros • No boiler plates • Becomes
fast, easy and fun Cons • Multiple Radio Buttons • RecyclerView in Edit mode -
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