It is not trivial to create an asynctask and monitor its progress on an activity's progress dialog.

The techniques you need to implement well include fragment dialogs, retained fragments, non-cancellable dialogs, or retained objects, that also can clean themselves up as various asynchronous events take place in the system.

With out a proper set of test cases it is very easy to miss the expected behavior.

Here is a list of test cases that I can think of in my limited research on this item.

satya - 10/31/2013 11:09:01 AM

Here is a list

  1. Normal - No orientation change
  2. Flip once
  3. Flip back
  4. Multiple flips
  5. Back prevented
  6. Home
  7. Home and Back during the dialog
  8. Home and Back (dialog exits before back)
  9. Normal, Flip, Home, Back
  10. Accumulation of objects between multiple invocations
  11. If doable, a phone call simulation and back
  12. Asynctask that takes
  13. a few seconds, a few minutes

satya - 10/31/2013 11:11:24 AM

Key things to observe


Dialog is dismissed when the task finishes
Task is removed once the task finishes
Dialog is dismissed in the following cases correctly
   1. Activity is being restarted during finish
   2. Activity is hidden and brought back during finish

satya - 11/4/2013 11:11:34 AM

Another key test: Invoke the menu when the orientation has changed

This will validate the state of the activity once it is flipped. Invoking a menu option may expect an object to be available on the activity object. If this is not properly restored you will get a null pointer exception.

So this is a good test.

satya - 11/4/2013 11:14:49 AM

You should also ensure the async task object is not kept around once the task is complete.

Hopefully you are not using a pointer to the asynctask and holding it locally. Sometimes it does make sense to hold it as you may want to cancel the task on some UI action.

if you do hold it you will need to release it when the dialog or the task finishes.

Hopefully the asynctask will release itself if no is holding a pointer to itself.

eitherway it is good to track the number of instances of this task when possible.

satya - 11/4/2013 11:33:37 AM

Make sure the home and revisiting the activity while the activity finished works and closes the dialog

This is one of the test cases mentioned above.

Yo go home while the dialog is up. You return after a while and the task finishes meanwhile. You will see the dialog again because you could not have dismissed the dialog while the activity is in stopped state. So you will need to realize the dialog is still up on return although the task has finished.

So your fragment call back such as onstart or onresume must take into account the finish of the task and close the dialog and also set the task pointer to null to release i

satya - 11/12/2013 12:18:25 PM

Vestigial Testing: Reinvoke the action once, and many times

to see if there are any vestiges left from the previous one. For example a progress bar may have been left in its final state when the menu task has restarted. Or how many objects will stay behind after multiple invocations...

satya - 11/16/2013 3:24:23 PM

Here is an example of how to keep track of your testing efforts in code through comments


/*
 * To test an asynctask with an embedded progress bar
 * Uses retained fragments as a solution to get to the activity
 * 
 * Key Approach:
 * 
 * 1. Use a retained fragment to connect the activity to the async task
 * 2. use a progressbar to show the progress
 * 3. Control the progressbar as much as possible from the asynctask
 * 4. Takeover the view state of the progressbar here
 * 5. Because the activity is not doing a good job progressbar state
 * 6. Remove the asynctask from the retained fragment once the task
 *    finishes.
 * 7. See MyLongTaskWithFragments to see how the samething is
 *    done using dialogs instead of progressbars
 * 8. Cancels the task on an activity back  
 * 
 * Key testcases:
 * ****************************
 * 1. Regular orientation (Do it 3 times) (success)
 * 2. Flip in the middle (success)
 * 3. Flip and back (success)
 * 4. home and back during
 * 5. home and back after finishing
 * 6. Phone call hiding the activity
 * 7. prevent a back during the task Or cancel task on back 
 * 9. Go back before the task
 * 10. Go back during the task
 * 11. Go back after the task
 * 
 * Key secondary test cases
 * **************************************
 * 1. Cleanup the self pointer (success)
 * 2  Vestigial Testing: Retest the menu (Subsequent invocations may show vestiges)
 * 3. Flip back after finish a few times back and forth (Success)
 *    
 * 1. Regular orientation (Do it 3 times)
 * **************************************
 * See the progress bar
 * See the progress
 * Finish the progress
 * Regain the space
 * Asynctask pointer removed
 * No exceptions in log file
 * 
 * Redo it 3 times
 * 
 * 2. Flip in the middle
 * ***************************************
 * See the progress bar
 * See the progress
 * Flip
 * Pick up the progress where left off
 * Finish the progress
 * Regain the space
 * Asynctask pointer removed
 * No exceptions in log file
 * 
 * Rerun in the new orientation
 * Finish well
 * Regain space
 * No exceptions
 *
 * 3. Flip and back
 * *******************
 * See the progress bar
 * See the progress
 * Flip
 * Pick up the progress where left off
 * Flip again
 * Pick up the progress where left off
 * Finish in the original orientation
 * 
 * Asynctask pointer removed
 * No exceptions in log file
 * 
 * 4. home and back during
 * ***************************
 * See the progress bar
 * See the progress
 * Go home
 * async should be proceeding
 * progress should be proceeding in the background
 * Revisit the app
 * Pick up the progress where left off
 * Finish the progress
 * Regain the space
 * Asynctask pointer removed
 * No exceptions in log file
 * Retest in the same orientation if it works still
 *   
 * 5. home and back after finishing
 * *************************************
 * See the progress bar
 * See the progress
 * Go home
 * async should be proceeding
 * progress should be proceeding in the background
 * wait long enough for the task to finish
 * 2) possibilities
 * 1 is finishes and removes itself
 * 2 is finishes but waits for the restart
 * 
 * I think I used approach 2
 * 
 * Revisit the activtity
 * bar closed
 * async pointer reclaimed
 * 
 * 9. Go back before the task
 * ***************************** 
 * Go back without invoking the activity
 * Make sure nothing is broken
 * Check debug messages
 * we do this because the plumbing may effect initial conditions
 * eventhough the task hasn't started.
 * 
 * 10. Go back during the task
 * ***************************** 
 * See the progress bar
 * See the progress
 * Go back
 * Should see in debug that the task is cancelled
 * The task pointer is freed
 * You can revisit the activity
 * reinvoke the task
 * Complete task successfully
 * 
 * 11. Go back during the task
 * ***************************** 
 * See the progress bar
 * See the progress
 * wait the progressbar to close
 * The task pointer is freed
 * Go back
 * Should see in debug that the right close messages are observed
 * You can revisit the activity
 * reinvoke the task
 * Complete task successfully
 * 
 */
public class MyLongTaskWithProgressBar 
extends AsyncTask<String,Integer,Integer>
implements IWorkerObject
{
   public String tag = null;
   public static String PROGRESS_DIALOG_FRAGMENT_TAG_NAME="AsyncDialog";

   private MonitoredFragment retainedFragment;
   
   //To report back errors
   //Represents the BaseTester
   //This should track the activity status
   //and likely a RADO itself
   private IReportBack r;
   
   //To track current progress
   int curProgress = 0;
   
   MyLongTaskWithProgressBar(MonitoredFragment parentFragment
         ,IReportBack inr
         , String inTag)
   {
      tag = inTag;
      retainedFragment = parentFragment;
      r = inr;
   }
   
   //Gets executed in response to task.execute()
   protected void onPreExecute()
   {
      //Runs on the main ui thread
      Utils.logThreadSignature(this.tag);
      //show the dialog
      showProgressBar();
   }

   /*
    * I am going to start the asynctask
    * show the progress bar
    * we assume the activity is available here
    */
   private void showProgressBar()
   {
      Activity act = retainedFragment.getActivity();
      //assume activity is available
      ProgressBar pb = (ProgressBar) act.findViewById(R.id.tpb_progressBar1);
      pb.setProgress(0);
      pb.setMax(15);
      pb.setVisibility(View.VISIBLE);
   }
   
   
   //this can be null if the activity is not available
   private ProgressBar getProgressBar()
   {
      Activity act = retainedFragment.getActivity();
      if (act == null)
      {
         Log.d(tag, "activity is null. shouldnt be. returning a null dialog");
         return null;
      }
      //Good dialog
      return (ProgressBar) act.findViewById(R.id.tpb_progressBar1);
   }
   
    protected void onProgressUpdate(Integer... progress) 
    {
      //Runs on the main ui thread
      Utils.logThreadSignature(this.tag);
      this.reportThreadSignature();
      
      //will be called multiple times
      //triggered by publishProgress
       Integer i = progress[0];
       
       //Because this is called on the main thread
       //The following check may not be needed
       //if the activity is being re created
       //I could assume this method is delayed
       //until the activity is ready to receive
       //its messages.
       if (retainedFragment.isUIReady())
       {
          r.reportBack(tag, "Progress:" + i.toString());
          //set progress
          //wpd.get().setProgress(i);
          setProgressOnProgressBar(i);
       }
       else
       {
          Log.d(tag,"Activity is not ready! Progress update not displayed:" + i);
       }
    }

    private void setProgressOnProgressBar(int i)
    {
       this.curProgress = i;
       ProgressBar pbar = getProgressBar();
       if (pbar == null)
       {
          Log.d(tag, "Activity is not available to set progress");
          return;
       }
       r.reportBack(tag, "pbar:" + pbar.getProgress());
       pbar.setProgress(i);
       
    }
    private void closeProgressBar()
    {
       ProgressBar pbar = getProgressBar();
       if (pbar == null)
       {
          Log.d(tag, "Sorry progress bar is null to close it!");
          return;
       }
       //Dismiss the dialog
       Log.d(tag,"Progress bar is being dismissed");
       pbar.setVisibility(View.GONE);
       detachFromParent();
    }
    
    //when you start say you are not done
    //Mark it done on postExecute
    private boolean bDoneFlag = false;
    protected void onPostExecute(Integer result) 
    {         
      //Rember it to fix it when coming back up
      bDoneFlag = true;
      
      //Runs on the main ui thread
      Utils.logThreadSignature(this.tag);
      conditionalReportBack("onPostExecute result:" + result);
      
      //we need to close the dialog. However....
      //closeProgressDialog()
      
      //Conditions I ahve to take into account
      //1. activity not there
      //2. Activity is there but stopped
      //3. Activity is UI ready
      if (retainedFragment.isUIReady())
      {
         Log.d(tag,"UI is ready and running. dismiss is pemissible");
         closeProgressBar();
         return;
      }
      //either not there or stopped
      Log.d(tag,"UI is not ready. Do this on start again");
    }
    protected Integer doInBackground(String...strings)
    {
      //Runs on a worker thread
       //May even be a pool if there are 
       //more tasks.
      Utils.logThreadSignature(this.tag);
      
       for(String s :strings)
       {
          Log.d(tag, "Processing:" + s);
          //r.reportTransient(tag, "Processing:" + s);
       }
       for (int i=0;i<15;i++)
       {
          Utils.sleepForInSecs(2);
          publishProgress(i);
       }
       return 1;
    }
    protected void reportThreadSignature()
    {
       String s = Utils.getThreadSignature();
         conditionalReportBack(s);
    }
    private void conditionalReportBack(String message)
    {
       Log.d(tag,message);
       r.reportBack(tag,message);
    }
    
    /**
     * Call this from your onStart
     * @param act
     */
   public void onStart(Activity act) {
      
      //dismiss dialog if needed
      if (bDoneFlag == true)
      {
         Log.d(tag,"On my start I notice I was done earlier");
         closeProgressBar();
         return;
      }
      Log.d(tag,"I am reattached. I am not done");
      setProgressBarRightOnReattach();
   }
   
   private void setProgressBarRightOnReattach()
   {
      ProgressBar pb = getProgressBar();
      pb.setMax(15);
      pb.setProgress(curProgress);
      pb.setVisibility(View.VISIBLE);
   }
    
    
   private void detachFromParent()
   {
      if (client == null)
      {
         Log.e(tag,"You have failed to register a client.");
         return;
      }
      //client is available
      client.done(this,workerObjectPassbackIdentifier);
   }
   
   //To tell the called that I have finished
   IWorkerObjectClient client = null;
   int workerObjectPassbackIdentifier = -1;
   
   public void registerClient(IWorkerObjectClient woc,
         int inWorkerObjectPassbackIdentifier) {
      Log.d(tag,"Registering a client for the asynctask");
      client = woc;
      this.workerObjectPassbackIdentifier = inWorkerObjectPassbackIdentifier;
   }
   
   public void releaseResources()
   {
      //Cancel the task
      Log.d(tag,"Cancelling the task");
      cancel(true);
      //Remove myself
      detachFromParent();
   }

}//eof-class