Showing posts with label Android SDK. Show all posts
Showing posts with label Android SDK. Show all posts

Wednesday, August 21, 2013

Implementing Parcelable interface

When looking for a way to implement Parcelable interface, I found a great parcelabler tool by Dallas Gutauckis to generate code quickly.

Suppose we have a class:

 public class DO {  
      private Long id;  
      private String name;  
      private String desc;  
      private Date created;  
 }  

Then copy and paste into the textbox and click Build button, the result will be:

 public class DO {  
      private Long id;  
      private String name;  
      private String desc;  
      private Date created;  
   protected DO(Parcel in) {  
     id = in.readLong();  
     name = in.readString();  
     desc = in.readString();  
     long tmpCreated = in.readLong();  
     created = tmpCreated != -1 ? new Date(tmpCreated) : null;  
   }  
   public int describeContents() {  
     return 0;  
   }  
   public void writeToParcel(Parcel dest, int flags) {  
     dest.writeLong(id);  
     dest.writeString(name);  
     dest.writeString(desc);  
     dest.writeLong(created != null ? created.getTime() : -1L);  
   }  
   public static final Parcelable.Creator<DO> CREATOR = new Parcelable.Creator<DO>() {  
     public DO createFromParcel(Parcel in) {  
       return new DO(in);  
     }  
     public DO[] newArray(int size) {  
       return new DO[size];  
     }  
   };  
 }  

Although the options for removing fields won't work, but the tool is useful. Thank you Dallas.

Thursday, August 15, 2013

Custom BaseAdapter and notifyDataSetChanged

When making a custom adapter from BaseAdapter, I got the problem that calling notifyDataSetChanged() doesn't work - new data isn't refreshed as expected.

Below is my code:

 public class RowAdapter extends BaseAdapter {  
   private List<? extends RowDO> data;  
   public RowAdapter(List<? extends RowDO> d) {  
     data = d;  
   }  
   @Override  
   public int getCount() {  
     return data.size();  
   }  
   ...  
 }  
 public class MainActivity extends Activity {  
   private RowAdapter rowAdapter;  
   private List<RowDO> listDO;  
   ...  
   private void displayList(){  
     listDO = getListDO();  
     if (rowAdapter == null) {  
       rowAdapter = new RowAdapter(listDO);  
       ListView listView = (ListView) findViewById(R.id.list);  
       listView.setAdapter(rowAdapter);  
     } else {  
       rowAdapter.notifyDataSetChanged();  
     }  
   }  
   private List<RowDO> getListDO() {  
     List<RowDO> list = new ArrayList<RowDO>();  
     list.add(new RowDO());  
     if (some_conditions) {  
       ...  
     }  
     return list;  
   }  
 }  

After google around, I found Snicolas answer

The list I use in my adapter (data) is a reference to the list created in getListDO() when passing it to adapter at first time. So the next time, when I re-assign the listDO with the new list, the old copy in the adapter is still there.

To correct this, we should keep the original list as below code:

 private List<RowDO> listDO = new ArrayList<RowDO>();  
 private void displayList(){  
   listDO.clear();  
   listDO.addAll(getListDO());  
   if (rowAdapter == null) {   
     rowAdapter = new RowAdapter(listDO);   
     ListView listView = (ListView) findViewById(R.id.list);   
     listView.setAdapter(rowAdapter);   
    } else {   
     rowAdapter.notifyDataSetChanged();   
    }   
 }  

Tuesday, August 6, 2013

Android ContentProvider

When an Android app gets into more complex with AsyncTasks to manipulate the SQLite database, we faced with the database locking issues.

So I googled around and found this discussion, then switched to ContentProvider and it solves the problem.

To implement ContentProvider, the first thing to do is adding authorities to AndroidManifest.xml

 <provider android:authorities="com.mycompany.androidapp.contentprovider.authorities"   
       android:name="com.mycompany.androidapp.data.ContentProviderDb"/>  

The android:authorities="com.mycompany.androidapp.contentprovider.authorities" is just an attribute - whatever unique value - which will be used for the URI to access the data

The class com.mycompany.androidapp.data.ContentProviderDb is extended from android.content.ContentProvider, must implement following required methods:

 public boolean onCreate();  
 public String getType(Uri uri);  
 public Cursor query(Uri uri, String[] projection, String selection,  
                String[] selectionArgs, String sortOrder);  
 public Uri insert(Uri uri, ContentValues values);  
 public int update(Uri uri, ContentValues values, String selection,  
                String[] selectionArgs);  
 public int delete(Uri uri, String selection, String[] selectionArgs);  

Because we need to access the database from different threads, so we must implement the access thread-safe. The simple way is to use synchronized for those query/insert/update/delete methods.

Below is an example

 public class ContentProviderDb extends ContentProvider {  
   private SQLiteOpenHelper openHelper = null;  
   /****************************************  
    * ContentProvider implementation methods  
    * **************************************/  
   @Override  
   public boolean onCreate() {  
     openHelper = new SQLiteOpenHelper(getContext(), Constant.DATABASE_NAME, null , 1){  
       public void onCreate(SQLiteDatabase db){  
       }  
       public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){  
       }                 
     };  
     return true;  
   }  
   @Override  
   public String getType(Uri uri) {  
     // TODO Auto-generated method stub  
     return null;  
   }       
   /**Execute query: raw query (select/insert/update/delete), create, select data from table  
    * @return Cursor or null  
    */    
   @Override  
   public synchronized Cursor query(Uri uri, String[] projection, String selection,  
           String[] selectionArgs, String sortOrder) {  
     SQLiteDatabase database = openHelper.getReadableDatabase();  
     Cursor cursor = null;  
     String query = getQuery(uri);  
     String lcQuery = query.toLowerCase();  
     if (lcQuery.startsWith("select ") || lcQuery.startsWith("insert ") ||   
       lcQuery.startsWith("update ") || lcQuery.startsWith("delete ") ) { //raw queries  
       cursor = database.rawQuery(query, null);  
     } else if (lcQuery.startsWith("create ")) { //create tables  
       database.execSQL(query);  
     } else { //query is a table name -> normal select from table  
       cursor = database.query(query, projection, selection, selectionArgs, null, null, sortOrder);  
     }  
     return cursor;  
   }  
   @Override  
   public synchronized Uri insert(Uri uri, ContentValues values) {  
     String table = getQuery(uri);  
     SQLiteDatabase database = openHelper.getWritableDatabase();  
     long insertedId = database.insert(table, null, values);  
     return Uri.parse("content://" + getAuthority() + "/" + table + "/" + insertedId);   
   }  
   @Override  
   public synchronized int update(Uri uri, ContentValues values, String selection,  
           String[] selectionArgs) {  
     String table = getQuery(uri);  
     SQLiteDatabase database = openHelper.getWritableDatabase();  
     return database.update(table, values, selection, selectionArgs);  
   }  
   @Override  
   public synchronized int delete(Uri uri, String selection, String[] selectionArgs) {  
     String table = getQuery(uri);  
     SQLiteDatabase database = openHelper.getWritableDatabase();  
     return database.delete(table, selection, selectionArgs);  
   }  
   /***********************************************  
    * ContentProvider implementation methods - END  
    * *********************************************/  
   /**Get authority  
    * @return authority string  
    */  
   private String getAuthority() {  
     return "com.mycompany.androidapp.contentprovider.authorities";  
   }    
   /**Extract query from uri  
    * @param uri  
    * @return extracted query/table name  
    */  
   private String getQuery(Uri uri){  
     return uri.getPath().replace("/", "");//remove '/'  
   }  
 }  


To create tables and populate the database, we can either add some methods to onCreate of SQLiteOpenHelper in onCreate of ContentProviderDb:

 openHelper = new SQLiteOpenHelper(getContext(), Constant.DATABASE_NAME, null , 1){  
   public void onCreate(SQLiteDatabase db){  
     createTables(); //your own implementation 
     autoPopulate(); //your own implementation 

Or in the activity (with copy from existing database):

 private String database_path = "";  
 if(android.os.Build.VERSION.SDK_INT >= 4.2){  
   database_path = getBaseContext().getApplicationInfo().dataDir + "/databases/";   
 } else {  
   database_path = "/data/data/" + getBaseContext().getPackageName() + "/databases/";  
 }  
 if(!existDataBase()) {  
   try {  
     if (!copyDataBase()) {  
       getContext().getContentResolver();//init the ContentProviderDb to create the database  
       createTables();  
       autoPopulate();  
     }  
   } catch (IOException e) {  
       Log.e("IOException", "exception in copyDataBase() method");  
   }  
 }  
 /**Check if database exist  
  * @return true if the database is existed, false if not  
  */  
 private boolean existDataBase() {  
   File dbFile = new File(getDatabasePath());  
   return dbFile.exists();  
 }  
 /**Get database path  
  * @return path string  
  */  
 private String getDatabasePath() {  
   return database_path + Constant.DATABASE_NAME;  
 }  
 /**Copy the existing database from the data directory  
  * @return true if the database is copied, false if not  
  * @throws IOException  
  */  
 private boolean copyDataBase() throws IOException {         
   File dbFile = getSourceDatabaseFile();// your own implementation
   if (dbFile.exists()) { //copy the database  
     InputStream in = new FileInputStream(dbFile);  
     OutputStream out = new FileOutputStream(getDatabasePath());  
     byte[] mBuffer = new byte[1024];  
     int mLength;  
     while ((mLength = in.read(mBuffer))>0) {  
       out.write(mBuffer, 0, mLength);  
     }  
     in.close();  
     in = null;  
     out.flush();  
     out.close();  
     out = null;  
     return true;  
   }  
   return false;  
 }       


To manipulate the data in the activity, we use ContentResolver instead ContentProvider.

 /**  
  * Creates tables  
  */  
 private void createTables(){                                
     for(String sql : getTableCreationSQLs()){  
         execSQL(sql);  //query starts with "create table "
     }  
 }  
 /**Used for executing query, start with create (not select/insert/delete/update)  
  * @param query  
  */  
 private void execSQL(String query) {  
     query(query, null, null, null);  
 }  
 /**Execute query or select data from table  
  * @param query : query or table name  
  * @param columns  
  * @param whereClause  
  * @param orderBy  
  * @return cursor  
  */  
 private Cursor query(String query, String[] columns, String whereClause, String orderBy){  
   return getBaseContext().getContentResolver().query(getUri(query), columns, whereClause, null, orderBy);  
 }  
 private Uri getUri(String query){  
   return Uri.parse("content://" + getAuthority() + "/" + query);  
 }