Published

Fri 03 October 2014

←Home

Android: A short primer

Android has  a different paradigm for application development. So , In this post, I will  explain a few of those concepts (as I understand) that lie at its core.

  1. Cursor:  To draw parallels,  a cursor object is just like a ResultSet in JDBC. It is used for iterating over the underlying data and just like a JDBC ResultSet object, it must also be closed.  The following snippet shows how to iterate over the result set.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    try {
                cursor = getDatabase().query(tableName,
                        columns, whereClause, whereArgs, groupBy, having, orderBy);
                if (cursor != null) {
    
                    while (cursor.moveToNext()) {
                       cursor.getString(cursor.getColumnIndexOf("COLUMN_NAME_TO_SELECT"));
    
                    }
                }
            } catch (Exception e) {
                Log.e("[" + tag+ "] ", "Exception Occured while fetching data", e);
                throw e;
    
            } finally {
                // make sure to close the cursor
                cursor.close();
    
            }
    

  2. Loader :  if you are aware of swing development, then you will know that the best practice is to perform long running tasks such as running a query, fetching data from a service, etc. on a separate thread, this is even more important to follow in android because if you  don’t your app will just crash.  A loader is basically what you use to perform those long running tasks .  So this is what you basically have to do,  In your activity or fragment implement the Loader callback methods,  which would be invoked when the loader has finished performing its long running tasks.  The long running tasks are performed in the loader class’s loadinbackground method.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    /**
    The loader class
    */
    public class ArticleLoader extends AsyncTaskLoader<Map<String,String>> {
    
    /**
         * This is where the articles are loaded
         */
    @Override
        public Map<String,String> loadInBackground() {
      //Perform the data fetch here
        }
    
    }
    

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    //the fragment class where you create the loader and wait for the loader to do its job
    public class ArticleFragment extends Fragment implements
    LoaderCallbacks<Object>{
           @Override
     public void onActivityCreated(Bundle savedInstanceState) {
        //See if the loader is there
     Loader<Object> loader = getActivity().getSupportLoaderManager().getLoader(LOADER_ARTICLE_SUMMARY);
      if (loader != null && !loader.isReset()) {
            //reset it as data needs to be loaded again because activity was created again
            getActivity().getSupportLoaderManager().restartLoader(LOADER_ARTICLE_SUMMARY, args, this);
            } else {
              //instantiate the loader
              getActivity().getSupportLoaderManager().initLoader(LOADER_ARTICLE_SUMMARY, args, this);
            }
    
      }
         @Override
     public Loader<Object> onCreateLoader(int id, Bundle args) {
     //instantiate the loader here depending upon loader id there could be several loaders that you might instantiate
          if(id==LOADER_ARTICLE_SUMMARY){
                String rawQuery="select _id ,  "+ FeedSQLLiteHelper.COLUMN_ARTICLE_TITLE+ " , "+
         FeedSQLLiteHelper.COLUMN_ARTICLE_READ+ " , "+ FeedSQLLiteHelper.COLUMN_ARTICLE_IS_FAVORITE
          +" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_DOWNLOADED+ " , "+ FeedSQLLiteHelper.COLUMN_ARTICLE_SUMMARY
           +" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_FULL_CONTENT+" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_CREATED_DATE
          +" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_URL +" from "+FeedSQLLiteHelper.TABLE_ARTICLES + " where _id = ?";
           String articleId=   args.getString(FeedSQLLiteHelper.COLUMN_ID);
             Loader  loader=new SQLiteCursorLoader(this.getActivity().getApplicationContext(),fdb.getDbHelper(), rawQuery, new String []{articleId});
                          return loader;
                }
             else {
                     Loader loader= new ArticleLoader(getActivity().getApplicationContext(),id,args);
                  return loader;
               }
     }
     @Override
     public void onLoadFinished(Loader<Object> loader, Object data) {
        /** Here you are supplied with the data, so you could update the cursor of the adapter
         Just make sure you check the loader id before updating the adapter
      */
            int id=loader.getId();
            if(id==LOADER_ARTICLE_SUMMARY){
           Cursor cursor=(Cursor)data;
           if(!cursor.isClosed()){
               //do pojo conversion
              article=cursorToFeedArticle(cursor);
              loadDataIntoWebView(article);
    
           }
            }
         else{
             Map<String,String> result=(Map)data;
                article.setArticleContent(result.get("ARTICLE_FULL_CONTENT"));
              article.setArticleDownloaded(result.get("ARTICLE_DOWNLOADED"));
              loadDataIntoWebView(article);
            }
     }
     @Override
     public void onLoaderReset(Loader<Object> arg0) {
            /** remove references to the data of the loader if its a cursor just dereference it
           * In this case since we do not have a cursor leave it empty.
          */
    
    
      }
    
          }
    

  3. Adapter:  This is used to hold the data to view binding. In other words, this object holds data which can be accessed by the view and the data to view binding information which tells how that data is mapped to the  view components of the view.

  4. Activity: An activity is like a page. It represents a view which might be composed of several child views (fragments) or just hold its own view.

  5. Fragment:  It is a child view, it has its own layout, but it exists only within an activity. As a fragment can be reused within different activities, so it should not hold any activity specific references.  You can attach the fragment to an activity at runtime using FragmentManager API’s , but if the fragment is listed in the activity’s layout then it cannot be removed. So, generally it is better to use the FragmentManager API’s then to have this restriction.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    articleFragment = (ArticleFragment) getSupportFragmentManager()
                        .findFragmentByTag(ArticleFragment.tag);
    
    if (articleFragment == null) {
    //create a new instance of the fragment
                  articleFragment = new ArticleFragment();
                  Bundle args = new Bundle();
                   args.putString("KEY","VALUE");
                    articleFragment.setArguments(args);
                   //replace the framelayout with the article fragment's content
                 getSupportFragmentManager()
                           .beginTransaction()
                           .replace(R.id.content_frame_article_fragment,
                                 articleFragment, ArticleFragment.tag).commit();
    }
    
    else {
                   // create a method onUpdateFromActivity, just supply it with new arguments
                  articleFragment.onUpdateFromActivity(args);
               }
    

    Also Fragment instances can be retained, but in that case its lifecycle is different. It is better to have them retained because obviously it speeds up your application.
  6. Intent and Intent Filter: Intent is basically an object which is used to invoke another activity, a service instance, etc. You generally supply it with an action when invoking IntentService instance.   IntentFilter on the other hand is used to filter out what actions can a particular service, activity or a fragment can handle.

  7. IntentService and BroadcastReceiver:  IntentService listens for intent actions and maintains a queue of requests which are handled one after the other sequentially. It runs on a separate background thread, so one can perform synchronous tasks on it.  You must implement its onHandleIntent method to perform the tasks required. Once the task is completed, you should broadcast an intent signifying so and in your activity or fragment listen for the intent using BroadcastReceiver.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    //1. start an intentservice
    Intent feedPullServiceIntent = new Intent(this.getActivity().getApplicationContext(), PullFeedsService.class);
                  feedPullServiceIntent.setAction(ACTION_UPDATE_FEEDS);
                 feedPullServiceIntent.putExtra("feedName", feedName);
               getActivity().getApplicationContext().startService(feedPullServiceIntent);
    
    
    public class PullFeedsService extends IntentService {
    
    //2. handle the action
    @Override
      protected void onHandleIntent(Intent intent) {
        if(action==ACTION_UPDATE_FEEDS){
              if(isConnected()){
                //fetch and parse the feeds
               .....
    
              //3. notify completion
               notifyFetchComplete(ACTION_FEED_FETCH_COMPLETE,feedName);
             }
    
       }
         if(action==DELETE_FEEDS){
         //delete the feeds in the batch
       notifyFetchComplete(ACTION_DELETE_COMPLETE,feedName);
         }
         }
         private void notifyFetchComplete(String action, String feedName) {
             Intent i = new Intent();
              i.setAction(action);
              Bundle result=new Bundle();
           if(action==ACTION_FEED_FETCH_COMPLETE){
            Bundle result=new Bundle();
               result.putString("KEY", "VALUE");
           }
             else if(action==ACTION_DELETE_COMPLETE){
               Bundle result=new Bundle();
               result.putString("KEY", "VALUE");
           }
             i.putExtras(result);
             //4. Brodcast the event
            LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i);
        }
    
    //in your fragment or activity
    
     private BroadcastReceiver receiver = new BroadcastReceiver() {
            //5. called only when the fragment receives the specific intents  as specificied in the intent filter while registering
            @Override
                 public void onReceive(Context context, Intent intent) {
    
                    if(intent.getAction()==PullFeedsService.ACTION_FEED_FETCH_COMPLETE){
                      if(refreshItem!=null){
                          refreshItem.getActionView().clearAnimation();
                           refreshItem.setActionView(null);
                          }
                     Bundle result=intent.getExtras();
                     String refreshForFeed=result.getString("RefreshForFeed");
                       if(refreshForFeed.equals("ALL")||refreshForFeed.equals(feedName)){
                       //reset loaders
                         resetLoaders();
                      }
    
    
                  }
    
                  }
           };
    
    @Override
     public void onPause() {
       ...
        //fragment paused so unregister the receiver
        LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).unregisterReceiver(receiver);
    
    }
    
    @Override
     public void onResume() {
      ...
        //0. fragment running so register the receiver to listen for events
      //specify a particular intent filter
      IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(PullFeedsService.ACTION_FEED_FETCH_COMPLETE);
            intentFilter.addAction(PullFeedsService.ACTION_NOT_CONNECTED);
         LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()).registerReceiver(receiver, intentFilter);
    
    }
    


That’s it for now. In future, I might expound upon these individual elements,.

Go Top
comments powered by Disqus