Tuesday, December 25, 2012

Storing objects in an Android UI View

Preface
Its really useful to relate data with UI presentation of that data. By doing this one can identify the data that needs to be operated upon. In Android we can store data in a View by using View's setTag().


Scenario
Consider you have a requirement to show a list of purchase policies for a product. In each row of that list we have a Checkbox and one TextView. CheckBox shows if the policy has been applied on the product or not and TextView shows the actual text of the policy. Now, when a user selects or deselects a CheckBox we want to refresh and recalculate the total price of that purchase. For this recalculation we must know which policy is selected or deselected.
We have to somehow associate some data about each policy to each CheckBox. By doing this we can find out which policy is selected.

Problem
Associating data with its UI representation.

Solution  
In our design we have incorporated a ListView . List's data is coming from an adapter. In the getView of that adapter we are associating an object to each CheckBox. If you look at our inflated layout "purchase_policy_list_row" in onClick of our checBox we have a method to be invoked. When the method in called we can cast the passed View and retrieve our corresponding data.

Lets look at a sample code below. Here we also used view holder pattern. This is for making Listview's more efficient You can find more about it on the web.  Please notice the line in red. There we are creating a DTO object and setting it to a CheckBox. In our case we are keeping and Id and a type in our DTO.

 public class PurchasePolicyListAdapter extends BaseAdapter {  
      ...  
      @Override  
      public View getView(int position, View convertView, ViewGroup parent) {  
           View view = null;  
           //View holder pattern  
           if (convertView == null) {  
                LayoutInflater inflater = (LayoutInflater) context  
                          .getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
                view = inflater.inflate(R.purchase_policy_list_row, parent, false);  
                ViewHolder holder = new ViewHolder();  
                holder.policyPr = (CheckBox) view.findViewById(R.id.policyPr);  
                holder.policyTitle = (TextView) view.findViewById(R.id.policyTitle);  
                view.setTag(holder);  
           } else{  
                view = convertView;  
           }  
           ((ViewHolder) view.getTag()).policyPr.setTag(createTagObject(position));  
           ViewHolder holder = (ViewHolder) view.getTag();  
           holder.policyTitle.setText(policies.get(position).getPolicyText());  
           return view;  
      }  
      private PolicyDTO createTagObject(int position) {  
           PolicyDTO dto = new PolicyDTO(policies.get(position).getType(), policies.get(position).getPurchasePolicy().getId());  
           return dto;  
      }  
      //Class for view holder pattern  
      static class ViewHolder {  
           public CheckBox policyPr;  
           public TextView policyTitle;  
      }  
 }  

In R.purchase.order_policy_list_row, the CheckBox has android:onClick="policySelected" . This will trigger policySelected() in the PurchaseActivity class (Android will do this call automatically). When the method is called we are casting the given View to a CheckBox. Then retrieving our DTO by getTag() method.

 public class PurchaseActivity extends Activity{  
           ...  
           public void policySelected(View view){  
                final CheckBox policyInt = (CheckBox) view;  
                DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {  
                     @Override  
                     public void onClick(DialogInterface dialog, int which) {  
                          switch (which){  
                          case DialogInterface.BUTTON_POSITIVE:  
                               ...  
                               if(policyInt.isChecked()){  
                                    ...  
                                    PolicyDTO dto = (PolicyDTO)policyInt.getTag();  
                                    ...  
                               }  
                               ...  
                               break;  
                          case DialogInterface.BUTTON_NEGATIVE:  
                               ...  
                               break;  
                          }  
                     }  
                };  
                DialogInterface.OnCancelListener cancelListener = new DialogInterface.OnCancelListener(){  
                     @Override  
                     public void onCancel(DialogInterface dialog) {  
                          ...  
                     }  
                };  
                AlertDialog.Builder builder = new AlertDialog.Builder(this);  
                AlertDialog dialog = builder.setMessage(R.string.message).setPositiveButton(R.string.confirm, dialogClickListener)  
                          .setNegativeButton(R.string.cancel, dialogClickListener)  
                          .setCustomTitle(R.string.title)  
                          .setOnCancelListener(cancelListener)  
                          .show();  
           }  
           ...  
 }  

Here we are showing a dialog to a user with positive and negative buttons. You can skip this dialog part and focus only on the red lines of code. As you can see if the user clicks on the positive button then we are retrieving the attached PolicyDTO. In the negative button you can just cancel the user operation. You might notice that we have a CancelListener as well. This is because if user decides to cancel or move away from the dialog is as if he decides to negate the operation.