Wednesday, October 10, 2012

EditView for Android: Combination of EditText and TextView

It is often that page of a particular informational content has 2 main objective, to view and edit at the same time, for instance, personal information page. The common solution for these 2 objectives is to have 2 views i.e. display view and edit view. Technically, there are usually 2 activities, one to display and one to edit the content, each of them has similar layout and similar coding as well. Moreover, when there is a change of the layout or additional fields, both activities have to be modified.

The root cause of such problem due to the technically, TextView and EditView only serving one purpose. TextView can be used to display data while EditView can be used to edit only. An idea to combine both together to have only single page to both display and edit the same content would beneficial to both developer and user. Less duplicate works for developer, maintenance would be come easier and user would have a more user friendly UI and experience by interact with only single place for both display and edit purpose. 

The idea of combining both display view and edit view is quite common for web application. However, android seems has no ready-to-use view available in the default framework. Hence, I developed a simple view called EditView, an combination of both TextView and EditText or simply Editable TextView (There is an editable attribute in TextView but I'm not sure how it works). 

A default EditView has an edit button next to an input/display field. Click on the edit button will toggle between edit mode and display mode. All the save and update to database or other storage files take place in that click. 


Also, it is often that there exist more than 1 field in a form and a better approach for multiple fields is to have master control button which will toggle the display/edit mode for all necessary fields. In this case, the edit button of EditView could be hidden.


I had many encounter of this display/edit problem and annoyed by the double work for both views so I decided to write a simple view for this problem. I hope this simple view could be handy in the display/edit view task for other Android developers as well.



Thursday, February 2, 2012

DataGrid Part 5 - Customized ListView as table

If you have not read the previous posts, you might want to start from the first post.

We will discuss the Customized ListView in this post. Customized ListView is actually the table view under the header. I use ListView to achieve this table appearance by customize it. In order to do that, we need following classes.
class cls_datagrid_adapter extends BaseAdapter {

    private Context mContext;
    private cls_datagrid.members_collection mc;
    
    public cls_datagrid_adapter(Context context, cls_datagrid.members_collection mc) {
        mContext = context;
        this.mc = mc;
    }

    public int getCount() {
        return mc.DATA_SOURCE.getRowSize();
    }

    public Object getItem(int position) {
     return mc.DATA_SOURCE.getRow(position);
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
     cls_item_view iv; 
     
     cls_datatable.cls_datarow data = mc.DATA_SOURCE.getRow(position);
     
        if(convertView == null)
        {
         iv = new cls_item_view(mContext, mc, data); 
        }
        else 
        {
         iv = (cls_item_view)convertView;
         iv.populate(data);
        }
        
        return iv;
    }
}

class cls_item_view extends LinearLayout {

 private TextView[] aryTextView;
 private Context mContext;
 private cls_spliter2_view mTxtContent;
 private cls_datagrid.members_collection mc;
 
 public cls_item_view(Context context, cls_datagrid.members_collection mc, cls_datatable.cls_datarow data) {
  super(context);
  mContext = context;
  this.mc = mc;
        
  setOrientation(HORIZONTAL);

  artTextView = new TextView[data.getColumnSize()];
  int intCellSpliter = 0;
        
  for(int i = 0; i < mc.COLUMN_STYLE.size(); i++)
  {
   aryTextView[i] = new TextView(mContext);
   aryTextView[i].setTextSize(DataCell.FontSize);
   aryTextView[i].setPadding(5,5);
   aryTextView[i].setBackgroundColor(DataCell.BackgroundColor);
   aryTextView[i].setText(data.get(mc.COLUMN_STYLE.get(i).getFieldName()));
   aryTextView[i].setSingleLine(true);
   aryTextView[i].setGravity(DataCell.Gravity);

   addView(artTextView[i], new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            
    if(intCellSpliter < mc.COLUMN_STYLE.size())
    {
     mTxtContent = new cls_spliter2_view(getContext(),mc, i);
     mc.SPLITER_VIEW.get(i).add(mTxtContent);
     addView(mTxtContent,new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
     intCellSpliter++;
    }
  }
 }
    
 public void populate(cls_datatable.cls_datarow data)
 {
  for(int i = 0; i <  mc.COLUMN_STYLE.size(); i++)
  {
   artTextView[i].setWidth(mc.COLUMN_STYLE.get(i).getWitdh() * mc.SCALE);
   artTextView[i].setText(data.get(mc.COLUMN_STYLE.get(i).getFieldName()));
  }
 }
}
Customize a ListView is quite a common task, there are a lot of examples in the Internet demonstrate this so I will briefly explain this.
  • cls_item_view takes 3 parameters in constructor. The last parameter of data typecls_datatable.cls_datarow is the record to be displayed at each row
  • All the data population happen in the foor lop in the constructor. It is similar to the initHeader(). Each TextView will be separated by a Splitter.
Back to the cls_datagrid class, we now have couple of new things to add in:
  • LIST_VIEW and DATAGRID_ADAPTER in members_collection
  • initListView() which basically initialize the ListView
  • 2 statements: initListView(); and addView(mc.LIST_VIEW); in create()
public class cls_datagrid extends LinearLayout{

 private Context mContext;
 private members_collection mc;
 private String strNoDataText;
 private LinearLayout mDataGridHeader;

 class members_collection{
  public ArrayList<column> COLUMN_STYLE;
  public cls_datatable DATA_SOURCE;
  public ListView LIST_VIEW;
  public cls_datagrid_adapter DATAGRID_ADAPTER;
 }

 public static class column{
  
  private String strColumnName;
  private String strFieldName;
  private int intWidth;
  private int intIndex;
  
  public column(String DisplayName, String FieldName, int width)
  {
   strColumnName = DisplayName;
   strFieldName = FieldName;
   intWidth = width;
  }
  
  public void setIndex(int index)
  {
   intIndex = index;
  }
  
  public int getIndex()
  {
   return intIndex;
  }
  
  public String getColumnName()
  {
   return strColumnName;
  }
  
  public String getFieldName()
  {
   return strFieldName;
  }
  
  public int getWitdh()
  {
   return intWidth;
  }
  
  public void setWidth(int width){
   intWidth = width;
  }
 }

 public cls_datagrid(Context context , AttributeSet attrs) throws Exception {        
  super(context, attrs);
  setOrientation(VERTICAL);
  mContext = context;        
  mc = new members_collection();
  mc.COLUMN_STYLE = new ArrayList<column>();
 }

 public void addColumnStyle(column columnStyle)
 {
  mc.COLUMN_STYLE.add(columnStyle);
 }

 public void setNoDataText(String strText)
 {
  strNoDataText = strText;
 }

 public void refresh()
 {
  if(mDataGridHeader == null)create();
 }

 public void create()
 {
  mDataGridHeader = new LinearLayout(mContext);
  mDataGridHeader.setOrientation(LinearLayout.HORIZONTAL);
  mc.LIST_VIEW = new ListView(mContext);

  initHeader();
  initListView();

  addView(mDataGridHeader);
  addView(mc.LIST_VIEW);
 }

 private void initHeader()
 {
  cls_spliter_view Spliter;
  cls_header_view HeaderView;
  int intCellSpliter = 0;
  
  for(int i = 0; i < mc.COLUMN_STYLE.size(); i++)
  {  
   HeaderView = new cls_header_view(getContext(), mc);
   HeaderView.setText((mc.COLUMN_STYLE.get(i)).getColumnName());
   HeaderView.setTag(mc.COLUMN_STYLE.get(i));
   HeaderView.setWidth(mc.COLUMN_STYLE.get(i).getWitdh());
   mDataGridHeader.addView(HeaderView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
      
   if(intCellSpliter < mc.COLUMN_STYLE.size())
   {
    Spliter = new cls_spliter_view(getContext(), mc, i);
    mDataGridHeader.addView(Spliter,new LinearLayout.LayoutParams(5,5));
    intCellSpliter++;
   }
  }
 }
 
 private void initListView()
 {
  mc.DATAGRID_ADAPTER = new cls_datagrid_adapter(getContext(), mc);
  mc.LIST_VIEW.setDividerHeight(2);
  mc.LIST_VIEW.setAdapter(mc.DATAGRID_ADAPTER);
 }

 public void setDataSource(cls_datatable data)
 {
  mc.DATA_SOURCE = data;
 }

}

Sunday, January 22, 2012

DataGrid Part 4 - DataGrid Header

If you have not read the previous posts, you might want to start from the first post.

Again, referring to the code segments below, we are discuss the final step, DataGrid.refresh() in this post. As you might guess, this function eventually constructs the whole DataGrid.
DataGrid.setNoDataText("No data available");
DataGrid.addColumnStyle(new cls_datagrid.column("Header 1", "header_5", 50));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 2", "header_4", 80));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 3", "header_3", 130));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 4", "header_2", 120));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 5", "header_1", 80));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 6", "header_0", 140));
DataGrid.setDataSource(DataSource);
DataGrid.refresh();

The first thing to construct in DataGrid and it is also the part to be discussed in this post is the DataGrid header. As you might recall the screen of the DataGrid in the very first post as shown on the left.

The Header is referring to the top most of the DataGrid, which column header is placed i.e. "Header 1", "Header 2" and so on and so forth. The objective of this post is to build the basic header to hold the columns that defined and being added via the function DataGrid.addColumnStyle() above. The others functions like sorting & resizing will be discussed further in future posts.


Sunday, January 15, 2012

DataGrid Part 3 - DataGrid.column

If you have not read the previous posts, you might want to start from the first post.

We have discussed the DataSource in previous post, we will continue discussion on the DataGrid.column in this post. Just a re-cap, we have following codes in previous post. DataGrid.column is parameter to DataGrid.addColumnStyle() as shown below.
DataGrid.setNoDataText("No data available");
DataGrid.addColumnStyle(new cls_datagrid.column("Header 1", "header_5", 50));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 2", "header_4", 80));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 3", "header_3", 130));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 4", "header_2", 120));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 5", "header_1", 80));
DataGrid.addColumnStyle(new cls_datagrid.column("Header 6", "header_0", 140));
DataGrid.setDataSource(DataSource);
DataGrid.refresh();
DataGrid.column is a static class in DataGrid class. As you might notice, column is a very simple class with getter and setter methods.
public static class column{
  
  private String strColumnName;
  private String strFieldName;
  private int intWidth;
  private int intIndex;
  
  public column(String DisplayName, String FieldName, int width)
  {
   strColumnName = DisplayName;
   strFieldName = FieldName;
   intWidth = width;
  }
  
  public void setIndex(int index)
  {
   intIndex = index;
  }
  
  public int getIndex()
  {
   return intIndex;
  }
  
  public String getColumnName()
  {
   return strColumnName;
  }
  
  public String getFieldName()
  {
   return strFieldName;
  }
  
  public int getWitdh()
  {
   return intWidth;
  }
  
  public void setWidth(int width){
   intWidth = width;
  }
 }

Let's examine the class DataGrid, finally. :)
public class cls_datagrid extends LinearLayout{

 private Context mContext;
 private members_collection mc;
 private String strNoDataText;

 class members_collection{
  public ArrayList<column> COLUMN_STYLE;
 }

 public static class column{
  
  private String strColumnName;
  private String strFieldName;
  private int intWidth;
  private int intIndex;
  
  public column(String DisplayName, String FieldName, int width)
  {
   strColumnName = DisplayName;
   strFieldName = FieldName;
   intWidth = width;
  }
  
  public void setIndex(int index)
  {
   intIndex = index;
  }
  
  public int getIndex()
  {
   return intIndex;
  }
  
  public String getColumnName()
  {
   return strColumnName;
  }
  
  public String getFieldName()
  {
   return strFieldName;
  }
  
  public int getWitdh()
  {
   return intWidth;
  }
  
  public void setWidth(int width){
   intWidth = width;
  }
 }

 public cls_datagrid(Context context , AttributeSet attrs) throws Exception {        
  super(context, attrs);
  setOrientation(VERTICAL);
  mContext = context;        
  mc = new members_collection();
  mc.COLUMN_STYLE = new ArrayList<column>();
 }

 public void addColumnStyle(column columnStyle)
 {
  mc.COLUMN_STYLE.add(columnStyle);
 }

 public void setNoDataText(String strText)
 {
  strNoDataText = strText;
 }

}
Couple of things to note here.
  1. DataGrid extends LinearLayout
  2. member_collection class is just a placeholder to hold variables that to be shared amongst all the classes in the package.
  3. In the construction, we initialize the columns as an ArrayList and assign it to mc.COLUMN_STYLE
  4. function addColumnStyle(column) simply take DataGrid.column and put it in the ArrayList
  5. setNoDataText() is used to assign string value to display for an empty DataGrid
So we have an incomplete DataGrid class for now which has only DataGrid.column feature.

Next, DataGrid Part 4 - DataGrid Header

Saturday, January 7, 2012

Cross platform mobile application framework

I just learnt the are some cross platform mobile application framework which allows you to develop once and deploy to multiple platform , for example, application developed in Android and deployed into IOS.

The concept is to develop everything in HTML5, Javascript and CSS using the framework. The application then will be compiled via service provider and you will have your application in the platform you want.

One of the framework is called PhoneGap. What attracted me in PhoneGap is the storage features which I do not find in other framework.

Here is the features list.
  1. Accelerometer
  2. Camera
  3. Capture
  4. Compass
  5. Connection
  6. Contacts
  7. Device
  8. Events
  9. File
  10. Geolocation
  11. Media
  12. Notification 
  13. STORAGE
That's all about the good stuff. The bad thing is that you have to pay for it, it is not fully free. The service you have to pay for is the compilation in the cloud, PhoneGap Build. 



I'm not sure how it works. What is the definition of developer? and how to define public apps? Further look at the support issue, I realized that it is not cheap at all, even for basic, it costs $249.99 per year. It is clear that not every individual developer could affords it. It does come with a price uh! I can't find FAQ section in the website, hence I still cannot understand how's the pricing works. There is PhoneGap Build service included in the support package, does it mean the FREE for developer is never going to happen because I do need to pay even for the basic package. 

Another thing to note that, it is not easy to switch to another language, in this case, Javascript and the framework when you have already familiar with your current language so well. Simply put it, you have to rewrite everything from the start. It is not a easy task! However, the advantage of multiple platform deployment does outweigh the effort of re-development.  

Thursday, December 15, 2011

Object Modelling Part 2 - Business Object

In previous post, we have already define the data object to model the record in table. As a further step, we will then build the relationship between object and operation in business object.

Business object is simply the object wrap up the data object and relationship of the data objects. The purpose of having another object to represent the business logic is to have loosely coupled object design.

Consider we have 2 data classes, Transaction and Product which both represent transaction table and product table in database. The relationship of them is defined as Transaction has none or more Product. Operation in between can be seen as Transaction add or remote Product.

So, we define a business object called BoTxn.

class BoTxn{

 private Transaction mTransaction;
 private ArrayList mProductList;
 
 public class BoTxn(String txnID){
   mTransaction = new Transaction(txnID);
   mProcductList = new ArrayList();      
 }

 public void setTransactionTime(DateTime dt){
  Transaction.setDateTime(dt);
 }

 public void addProduct(Product Prd){
   mProductList.add(Prd);
 }

 public void removeProduct(int index){
   mProductList.remove(index);
 }

 public void submitTransaction(){
  //update to database
 }

}
Some sample methods are shown above. We could have another class Customer, for example, to be in the BoTxn. The same relationship and operation could be built for Customer and BoTxn.

The following code shows how to use in the code.
BoTxn txn = new BoTxn(1);
txn.addProduct(new Product(1));
txn.addProduct(new Product(2));
txn.submitTransaction();
On top of the relationship modelling above, I would like to share another modelling in data object in next post.

Monday, December 5, 2011

Object Modelling Part 1 - Data Object

I think object modelling isn't new concept, in fact I learnt it somewhere and added in few ideas that I thought of.

The basic idea of object modelling is that each object represent a row record in a table. All the relationships between tables are mapped in the object so that data in table could be accessed via object without access to data again and again.

To illustrate the concept, consider the following ClassA ( in C#)
class ClassA{

  private String mId;
  private Hashtable _HshTable;

  public ClassA(String Id){
    mId = Id;
  }

  public String property1{
    
    get{
       _getString("property_1");
    }

    set{
       _setString("property_1", value);
    }

  }

  public void refresh(){
    //populate the object with data from DB based on primary key mId to _HshTable;
  }

  public void save(){
    //update everything back to DB based on primary key mId.   
  }

  private _getString(String strProperty){
     if (_HshTable == null) refresh();
     return _HshTable[strProperty];
  }

  private _setString(String strProperty, String strValue){
     if (_HshTable == null) refresh();
     _HshTable[strProperty] = strValue;  
  }
}

The advantages of using object modelling is that we do not need to write code to access the database and join the tables every time if needed. We can access the data via format below.

ClassA ObjA = ClassA("Id01");
String value = ObjA.property1; //get value
ObjA.property1 = "Another value"; //set value

We can access the data in much simpler and structured method.

The next step, we could build operation and relationship to the object. Operation is based on business model. The relationship of the objects also are modelled and we could use it again and again without access the database. It is good idea to separate it to another object, i.e. one object to deal with database and another object to deal with business. From there, we can have a strong base for future extension.

Object Modelling Part 2 - Business Object