ADO: Activity Dependent Object implementation

satya - 11/25/2013 2:26:23 PM

See here what they are and the thought behind ADOs

See here what they are and the thought behind ADOs

satya - 11/25/2013 2:29:03 PM

What you will see here...

What you will see here is a list of files that implement the idea of ADOs and also how the ADOs are then used to solve the asynctask display problem. Of course you can solve the asynctask display problem with retained fragments as well. But doing so with ADO, although a bit retro, is a bit elegant as well and can be extended to work with fragments as well.

Keep in mind that these are experimental ideas and use them with your own risk and may require some work to make them rock solid.

satya - 11/25/2013 2:30:12 PM

IActivityDependentObject


/*
 * why?
 * 
 * I could have named it ActivityWorkerObject!
 * 
 * This object is dependent on an activity
 * it holds a pointer to the activity
 * 
 */
public interface IActivityDependentObject 
{
   public MonitoredActivityWithADOSupport getActivity();
}

satya - 11/25/2013 2:32:42 PM

MonitoredActivityWithADOSupport

This activity understands what ADOs are and pass messages to them so that they are aware of the activity state. Uses onRetainNonConfigurationInstance() method to anchor the retained state.


/*
 * What is this abstraction for?
 * 
 * To support Activity Dependent Objects (ADOs)
 * 
 * It will support a single retained root ADO object (root RADO)
 * 
 * if will give you a chance to create it one time 
 * for the lifecycle of the activity.
 * 
 * The root RADO will be maintained across config changes.
 * 
 * It will give an opportunity to create a RADO 
 * through an overrided method during the onStart.
 */
public class MonitoredActivityWithADOSupport 
extends MonitoredActivity
{
   private RetainedADO mRootRADO = null;
   
   //This will false when it is stopped
   //This will turn true on start
   private boolean bUIReady = true;
   
   public MonitoredActivityWithADOSupport(String intag) 
   {
      super(intag);
   }
   
   //use this method and cast the object
   //to your specific type.
   //This can be null if you have forgotten to set it up
   protected RetainedADO getRootRADO()
   {
      if (mRootRADO == null)
      {
         Log.w(tag,"you probably have forgotten to override onCreateRADO!");
      }
      return mRootRADO;
   }
   //The activity is going to be stopped
   //Doesn't mean it is going away
   //but again it might
   //
   //See if you can move it to onRetained method...
   //because, it might a stop but the actiity may come back
   //with its ui intact
   @Override
   protected void onStop() {
      //call super
      super.onStop();
      
      //Indicate that the UI is notready
      bUIReady = false;
      //indicate its unavailability
      if (mRootRADO != null)
      {
         //Make the activity null
         //mRootRADO.reset();
      }
   }
   
   //Reattache the activity to the root RADO
   @Override
   protected void onStart() {
      //onStart. UI may be partially visible.
      super.onStart();
      bUIReady = true;
      mRootRADO = obtainRootRADO();
      mRootRADO.attach(this);
   }
   
   private RetainedADO obtainRootRADO()
   {
      if (mRootRADO != null)
      {
         //this would be the case if the activity has restarted
         //and rootRADO stays intact
         return mRootRADO;
      }
      //rootRADO is null
      //see if is in the nonconfig instance
      Object rootRADO = this.getLastNonConfigurationInstance();
      if (rootRADO == null)
      {
         //The returned object is null
         //try to crate it
         rootRADO = onCreateRADO();
         return (RetainedADO)rootRADO;
      }
      
      //Last rootRADO is not null and retaiend
      if (!(rootRADO instanceof RetainedADO))
      {
         //it is not a retained ADO
         Log.e(tag,"You are returning non RetainedADO");
         return null;
      }
      //Last root RADO is there and it is the right type
      return (RetainedADO)rootRADO;
   }
   
   /**
    * Override this method to create your dependent objects
    * See obtainRootRADO to understand this
    * This is called during onStart
    * it may need to be moved to onCreate (or may be not)
    * Probably because the rootRADO may hold things
    * that are required to construct the UI as well
    * so it is then better to call it in onCreate
    * @return
    */
   protected RetainedADO onCreateRADO()
   {
      //default is null
      return null;
   }
   

   //Called only when there is a configuration change
   //Called between onStop and onDestroy
   //You can return a local object pointer to 
   //retrieve it back through getConfigurationInstance.
   @Override
   public Object onRetainNonConfigurationInstance() {
      Log.d(tag,"onRetainNonConfigurationInstance.");
      if (mRootRADO != null)
      {
         Log.d(tag,"mRootRADO is not null. Resetting its activity");
         //Make the activity null
         mRootRADO.reset();

      }
      return mRootRADO;
   }


   //You should call this if an activity is 
   //known to be legitly closing.
   //ex:
   //   a) explicit finish
   //   b) implied finish such as back
   //
   //An activity may also close for
   //   a) a config change to reborn
   //   b) low memory process is getting terminated
   protected void releaseResources()
   {
      if (mRootRADO != null)
      {
         Log.d(tag, "Releasing root RADO resources");
         mRootRADO.releaseContracts();
      }
   }
   
   /**
    * This will false when it is stopped.
    * This will turn true on start.
    * see bUIReady private variable.
    */
   public boolean isUIReady()
   {
      return bUIReady;
   }
}//eof-class

satya - 11/25/2013 2:36:49 PM

DefaultADO


/*
 * See IActivityDependentObject
 * See RecreatedADO
 * See RetainedADO
 * 
 * This acts as a place holder for common
 * member variables.
 * 
 * This class is abstract because the way 
 * an activity is passed depends on the derived class.
 * 
 */
public abstract class DefaultADO 
implements IActivityDependentObject
{
   //This variable should work well between
   //the same thread or between threads
   protected volatile MonitoredActivityWithADOSupport m_parent = null;
   
   //This is used for debug messages
   //you want direct access to child classes
   protected String tag = null;
   
   //this forces child classes to always provide a tag
   //Need for Default constructor is not needed
   //because this class is not going to be recreated by
   //the framework!
   //Implication is that you are not going to pass it 
   //through bundles!!
   public DefaultADO(String intag)
   {
      tag = intag;
   }
   
   public MonitoredActivityWithADOSupport getActivity()
   {
      return m_parent;
   }
   //The way an activity is set 
   //is through indirect means
   protected void setActivity(MonitoredActivityWithADOSupport a)
   {
      m_parent = a;
   }
}

satya - 11/25/2013 2:37:52 PM

IRetainedADO


/**
 * So what are its properties.
 * In addition to being an ADO it also implements the protocol of 
 * what it means to be retained.
 * 
 * See the RetainedADO class for more details
 * This is here to solve multiple-inheritance where previously
 * built Android SDK objects can behave like RetainedADOs.
 * Ex: An asynctask can be extended to behave like a RetainedADO
 * 
 * Implemented by: RetainedADO
 *  
 */
public interface IRetainedADO 
extends IActivityDependentObject
{
   public MonitoredActivityWithADOSupport getActivity();

   // Usually called on stop.
   // should set the activity to null
   // Call child ados reset
   public void reset();
   
   //Called when the activity is new
   //or reborn.
   //set the activity pointer
   //call any child ados
   public void attach(MonitoredActivityWithADOSupport act);
   
   //Called on destroy
   //Call children as well
   public void releaseContracts();
   
   //utility function
   //this will be true if activity is available
   //refactor later for additional activity statuses
   public boolean isActivityReady();
   
   //For this to be true
   //the activity must be there
   //the activity must not be stopped
   public boolean isUIReady();

   /**
    * Redirected from the underlying activity
    * @return true if the activity is being tagged for configuration change 
    */
   public boolean isConfigurationChanging();

   /**
    * Create a hierarchy of RADO objects
    * @param childRetainedADO
    */
   public void addChildRetainedADO(IRetainedADO childRetainedADO);

   /**
    * Better method to call from a constructor.
    * otherwise the previous method is invoking a virtual method
    * @param childRetainedADO
    */
   public void addChildRetainedADOOnly(IRetainedADO childRetainedADO);

   
   /**
    * You may need to remove this worker ADO once its job is done
    * @param childRetainedADO
    */
   public void removeChildRetainedADO(IRetainedADO childRetainedADO);
   
   /**
    * Not sure when you need to call this
    */
   public void removeAllChildRetainedADOs();
   
   /**
    * To support Named ADOs. These are useful
    * for repeated menu commands.
    *  
    * @return The name of this ADO if available or null otherwise
    */
   public String getName();
   
   /**
    * Unregister from the parent
    */
   public void detachFromParent();
   
   /**
    * It is important to know overtime how the 
    * RADO objects are accumulating
    */
   public void logStatus();
   
}//eof-interface

satya - 11/25/2013 2:38:35 PM

RecreatedADO


/*
 * This object is recreated along with the Activity
 * in its onCreate.
 * 
 * It lives and perishes with the activity object
 * 
 * useful in delegating tasks from activity
 * 
 * keeps the activity code clean
 * 
 * Key notes
 * ***************
 * 1. An activity can host any number of RecreatedADOs
 * 2. An activity can host only one RetainedADO root
 * 3. Because it is orchestrated through onNonConfiguration callback
 * 4. (how this will change if fragments are introduced is tbd)
 * 
 * Caution:
 * **************
 * 1. Do not pass this object to anyone that retains it
 * 2. Because it will be stale on activity restart
 * 3. There will be a loss of the activity object
 * 4. Use in that case RetainedADO
 */
public class RecreatedADO 
extends DefaultADO
{
   public RecreatedADO(MonitoredActivityWithADOSupport in, String intag)
   {
      super(intag);
      setActivity(in);
   }
   /*
    * This activity will never be null
    * because this object is not retained across
    * invocations of the activity restarts.
    */
   public MonitoredActivityWithADOSupport getActivity()
   {
      return super.getActivity();
   }
}

satya - 11/25/2013 2:39:08 PM

RetainedADO


/**
 * So what are its properties.
 * 
 * 1. this object, like a fragment, can stick around.
 * 
 * 2. the activity will be reset to null on stop.
 * 
 * 3. this should have the ability to traverse its child
 * dependent objects
 * 
 * 4. This ado should exist as long as the activity is
 * around including its rebirth. But it should release
 * its contracts and resources when the activity is
 * no longer there and destroyed
 * 
 * 5. there is a big assumption that no one holds a pointer
 * to this object than the activity framework
 * 
 * 6. So when the activity is destroyed it is expected that
 * this ado becomes available for garbage collection
 * 
 * 7. To construct a retainedADO you need either an activity
 * or another RetainedADO
 * 
 * Some design choices
 * **********************
 * 1. May be there are 2 kinds of retained ADOs
 * 2. A singular RADO hosted by an activity
 * 3. And multiple RADOs that can be hoseted by other RADOs
 * 4. For now I will work with what I have and refine the model later
 */
public class RetainedADO 
extends DefaultADO
implements IRetainedADO
{
   private String mName = null;
   
   //these are child ADOs
   //This parent ADO should call life cycle methods
   //on the child ADOs
   private ArrayList<IRetainedADO> childRetainedADOs = 
      new ArrayList<IRetainedADO>();
   
   //To provide support for detachFromParent
   IRetainedADO parentRADO = null;
   
   //
   public RetainedADO(Activity parent, String tag)
   {
      super(tag);
      //The parent activity is used as a merely guideline
      //to force a constructor that requires it.
      //This avoids the mistakes of creating RetainedADO
      //without a parent!
      //So in short the activity parent is ignored here.
      //In the future i may consider using it
      //but if I do I need to keep track of the activity states
      //to answer if the activity is ready.
   }
   
   //Because I am a reetainedADO I need to 
   //attach myself to a parent so that parent can call me
   //usually this parent is the activity
   //however other retainedADOs can act as parents
   public RetainedADO(IRetainedADO parent, String tag)
   {
      super(tag);
      parent.addChildRetainedADO(this);
      parentRADO = parent;
   }
   
   /**
    * This is used for multiple inheritance simulation needs.
    * Parent registration is lacking.
    * This becomes a delegate.
    * It works for the host.
    * 
    * parent is the same for both the
    *    a) delegate
    *    b) and the host
    * 
    * TBD: may be I should create another RADO called DelegatedRADO
    * @param tag
    */
   private IRetainedADO host = null;
   public RetainedADO(IRetainedADO parent, String tag, IRetainedADO inHost)
   {
      super(tag);
      parentRADO = parent;
      host = inHost;
      //Add the host for all the callbacks
      parent.addChildRetainedADOOnly(inHost);
      
      //Set the activity if available
      MonitoredActivityWithADOSupport act = parent.getActivity();
      if (act != null)
      {
         this.setActivity(act);
      }
   }
   
   /*
    * This object can be null
    * it will be null between stop and start
    */
   public MonitoredActivityWithADOSupport getActivity()
   {
      MonitoredActivityWithADOSupport a = super.getActivity();
      if (a == null)
      {
         Log.w(tag,"Activity is being asked when it is null");
      }
      return a;
   }
   
   //Usually called on stop
   public void reset()
   {
      Log.d(tag,"Activity is being set to null. It is being stopped");
      setActivity(null);
      resetChildADOs();
   }
   
   private void resetChildADOs()
   {
      for(IRetainedADO rado: this.childRetainedADOs)
      {
         rado.reset();
      }
   }
   
   //Called when the activity is new
   //or reborn
   public void attach(MonitoredActivityWithADOSupport act)
   {
      //Usually called on start
      //the activity is ready
      Log.d(tag,"Activity is being attached. called from onstart");
      setActivity(act);
      attachToChildADOs(act);
   }
   
   private void attachToChildADOs(MonitoredActivityWithADOSupport act)
   {
      for(IRetainedADO rado: this.childRetainedADOs)
      {
         rado.attach(act);
      }
   }
   //Called on destroy
   public void releaseContracts()
   {
      //nothing in the default
      Log.d(tag,"Most likely activity is getting destroyed. release resources");
      releaseChildADOContracts();
   }
   
   private void releaseChildADOContracts()
   {
      for(IRetainedADO rado: this.childRetainedADOs)
      {
         rado.releaseContracts();
      }
   }
   
   //utility function
   public boolean isActivityReady()
   {
      return (getActivity() != null) ? true : false;
   }
   
   public boolean isUIReady()
   {
      if (isActivityReady() == false)
      {
         return false;
      }
      //activity is there
      //Ask the activity if it is ready
      MonitoredActivityWithADOSupport act = getActivity();
      return act.isUIReady();
   }

   /**
    * Be wary to call this method from a constructor
    */
   public void addChildRetainedADO(IRetainedADO childRetainedADO)
   {
      this.childRetainedADOs.add(childRetainedADO);
      //Add the activity as this might happen out of sync with sets
      if (isActivityReady())
      {
         //activity is available
         childRetainedADO.attach(getActivity());
      }
   }
   /**
    * Better method to call from a constructor.
    * otherwise the previous method is invoking a virtual method
    * @param childRetainedADO
    */
   public void addChildRetainedADOOnly(IRetainedADO childRetainedADO)
   {
      this.childRetainedADOs.add(childRetainedADO);
   }
   public boolean isConfigurationChanging()
   {
      if (isActivityReady() == false)
      {
         throw new RuntimeException("Wrong state. Activity is not available");
      }
      return getActivity().isChangingConfigurations();
   }
   /**
    * You may need to remove this worker ADO once its job is done
    * @param childRetainedADO
    */
   public void removeChildRetainedADO(IRetainedADO childRetainedADO)
   {
      Log.d(tag,"Removing a childRetainedADO");
      this.childRetainedADOs.remove(childRetainedADO);
   }
   
   /**
    * Not sure when you need to call this
    */
   public void removeAllChildRetainedADOs()
   {
      Log.w(tag,"Removing all child RADOs. Beware of the side affects");
      this.childRetainedADOs.clear();
   }
   
   /**
    * To support Named ADOs. These are useful
    * for repeated menu commands.
    *  
    * @return The name of this ADO if available or null otherwise
    */
   public String getName()
   {
      return mName;
   }
   /**
    * Unregister from the parent.
    * If the parent is a  RADo it will work.
    * It has no impact if the parent is an activity
    * or if the current RADO is a root RADO.
    * 
    * In the future see if this behavior should apply to the root RADO as well.
    */
   public void detachFromParent()
   {
      if (parentRADO != null)
      {
         Log.d(tag,"Removing a child RADO from a parent");
         if (host != null)
         {
            Log.d(tag,"Removing the host child RADO from a parent");
            parentRADO.removeChildRetainedADO(host);
         }
         else
         {
            //host is null. this is the primary
            parentRADO.removeChildRetainedADO(this);
         }
      }
      else
      {
         Log.w(tag,"No parent RADO detected. This is likely a root RADO");
      }
   }
   public void logStatus()
   {
      Log.d(tag,"Number of Child RADO objects:" + this.childRetainedADOs.size());
      for(IRetainedADO rado: this.childRetainedADOs)
      {
         rado.logStatus();
      }
   }
}//eof-class

satya - 11/25/2013 2:44:48 PM

BaseTesterRADO: An example of a retained object


/*
 * Name: BaseTester RetainedADO
 * 1. Expected to be a member variable in an activity
 * 2. Encapsulates multiple functions in response to menu 
 * clicks.
 * 3. Act as a base class for other kinds of testers
 */
public class BaseTesterRADO 
extends RetainedADO
implements IReportBack
{
   public BaseTesterRADO(Activity act, String tag)   {
      super(act, tag);
      if (!(act instanceof IReportBack)) {
         throw new RuntimeException("Sorry, the activity should support IReportBack interface");
      }
   }
   public void reportBack(String tag, String message)   {
      if (!isActivityReady())      {
         Log.d(tag,"sorry activity is not ready during reportback");
         return;
      }
      //activity is good
      getReportBack().reportBack(tag,message);
   }
   
   private IReportBack getReportBack()   {
      return (IReportBack)getActivity();
   }

   public void reportTransient(String tag, String message)   {
      if (!isActivityReady())      {
         Log.d(tag,"sorry activity is not ready during reportback");
         return;
      }
      //activity is good
      getReportBack().reportBack(tag,message);
   }
}//eof-class

satya - 11/25/2013 2:45:49 PM

AsyncTesterRADO: Making use of BaseTesterRADO


public class AsyncTesterRADO
extends BaseTesterRADO
{
   private final static String tag="AsyncTesterRADO"; 
   public AsyncTesterRADO(Activity act) {
      super(act, tag);
   }

   //Call this from a menu item
   public void testFragmentProgressDialog()
   {
      //Check status when you start
      logStatus();
      
      //Instantiate an asynctask
      //
      //it is also a RADO object
      //it knows its life cycle
      
      //The task will create a fragment dialog
      //report its progress
      //Close the dialog when it is done
      
      //tbd: May be remove itself from the registry as well!!
      MyLongTaskWithFragmentDialog3 longTaskWithFragmentDialog3 = 
         new MyLongTaskWithFragmentDialog3(this,this,"Task With Dialogs");
      
      //Execute it
      longTaskWithFragmentDialog3.execute("String1","String2","String3");
   }
}

satya - 11/25/2013 2:47:03 PM

TestAsyncTaskDriverActivity: A driver to work the asyc tester


public class TestAsyncTaskDriverActivity 
extends MonitoredActivityWithADOSupport
implements IReportBack
{
   public static final String tag="AsyncTaskDriverActivity";
   
   //Establish these pointers during onCreate
   //they being retaiend objects;
   AsyncTesterRADO asyncTester = null;
   AsyncTesterFragment asyncTesterFragment = null;   

   public TestAsyncTaskDriverActivity() {
      super(tag);
   }
   AsyncTesterRADO getAsyncTester() {
      return (AsyncTesterRADO)getRootRADO();
   }
   /*
    * This is currently called from onStart
    * Move it to onCreate
    */
    @Override
    public RetainedADO onCreateRADO() {
       return new AsyncTesterRADO(this);
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        //I should be guranteed to have my root RADO here!
        //I should be able to add my retained fragments here
        //Having my retained objects here ensures 
        //I can call my menus later and be assured their
        //executors are available.
       asyncTester = getAsyncTester();
        
        asyncTesterFragment =
           AsyncTesterFragment.establishRetainedAsyncTesterFragment(this);
        
        setContentView(R.layout.main);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu){ 
       super.onCreateOptionsMenu(menu);
          MenuInflater inflater = getMenuInflater();
          inflater.inflate(R.menu.main_menu, menu);
       return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item){ 
       appendMenuItemText(item);
       if (item.getItemId() == R.id.menu_test_flip){
          asyncTester.testFragmentProgressDialog();
          return true;
       }
       if (item.getItemId() == R.id.menu_test_flip1){
          asyncTesterFragment.testFragmentProgressDialog();
          return true;
       }
       if (item.getItemId() == R.id.menu_test_pbars){
          startTargetActivity(TestProgressBarDriverActivity.class);
          return true;
       }
       if (item.getItemId() == R.id.menu_show_pbars){
          startTargetActivity(ShowProgressBarsActivity.class);
          return true;
       }
       if (item.getItemId() == R.id.menu_test_config_change) {
          startTargetActivity(TestConfigChangeActivity.class);
          return true;
       }
          return true;
    }
    private TextView getTextView(){
        return (TextView)this.findViewById(R.id.main_text1);
    }
    private void appendMenuItemText(MenuItem menuItem){
       String title = menuItem.getTitle().toString();
       TextView tv = getTextView(); 
       tv.setText(tv.getText() + "\n" + title);
    }
    private void emptyText(){
       TextView tv = getTextView();
       tv.setText("");
    }
    private void appendText(String s){
       TextView tv = getTextView(); 
       tv.setText(tv.getText() + "\n" + s);
       Log.d(tag,s);
    }
   public void reportBack(String tag, String message)
   {
      this.appendText(tag + ":" + message);
      Log.d(tag,message);
   }
   public void reportTransient(String tag, String message)
   {
      String s = tag + ":" + message;
      Toast mToast = 
        Toast.makeText(this, s, Toast.LENGTH_SHORT);
      mToast.show();
      reportBack(tag,message);
      Log.d(tag,message);
   }
}//eof-class

satya - 11/25/2013 2:48:09 PM

Here is a ProgressDialog encapsulated as a FragmentDialog


/**
 * A DiglogFragment that encapsulates a ProgressDialog
 * This is not expected to be a retained fragment dialog. 
 * Gets recreated as activity rotates.
 */
public class ProgressDialogFragment extends DialogFragment  {
   private static String tag = "ProgressDialogFragment";
   ProgressDialog pd; //Will be set by onCreateDialog
   
   //This gets called from ADOs such as retained fragments
   //typically done when acitivty is attached back to the asynctask
   private IFragmentDialogCallbacks fdc;
   public void setDialogFragmentCallbacks(IFragmentDialogCallbacks infdc)
   {
      Log.d(tag, "attaching dialog callbacks");
      fdc = infdc;
   }
   
   //This is a default constructor. Called by the framework all the time
   //for re-introduction.
   public ProgressDialogFragment()   {
      //Should be safe for me to set cancelable as false;
      //wonder if that is carried through rebirth?
      this.setCancelable(false);
   }
   //One way for the client to attach in the begining
   //when the fragment is reborn. 
   //The reattachment is done through setFragmentDialogCallbacks
   //This is a shortcut. You may want to use the newInstance pattern
   public ProgressDialogFragment(IFragmentDialogCallbacks infdc)   {
      this.fdc = infdc;
      this.setCancelable(false);
   }
   /**
    * This can get called multiple times each time the fragment is 
    * recreated. So storing the pointer should be safe and it will 
    * be a new one every time this fragment is recreated.
    */
   @Override
   public Dialog onCreateDialog(Bundle savedInstanceState)   {
      Log.d(tag,"In onCreateDialog");
      pd = new ProgressDialog(getActivity());
      pd.setTitle("title");
      pd.setMessage("In Progress...");
      pd.setIndeterminate(false);
      pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
      pd.setMax(15);
      return pd;
   }
   //Called when the dialog is dismissed.I should tell my corresponding task
   //to close or do the right thing!
   @Override
   public void onDismiss(DialogInterface dialog)   {
      super.onDismiss(dialog);
      Log.d(tag,"Dialog dismissed");
      if (fdc != null)      {
         fdc.onDismiss(this, dialog);
      }
   }
   @Override
   public void onCancel(DialogInterface dialog)   {
      super.onDismiss(dialog);
      Log.d(tag,"Dialog cancelled");
      if (fdc != null)   {
         fdc.onCancel(this, dialog);
      }
   }
   //will be called by a client
   public void setProgress(int value)   {
      pd.setProgress(value);
   }
}

satya - 11/25/2013 2:54:03 PM

Bringing it all together a well behaved AsyncTask

This asynctask does not use retained fragments but still achieves the goal of communicating with its UI counter parts in the activity. There are other articles here where a retained fragment is used instead of a retained ADO.


/*
 * To test a progress dialog on flip
 * first make the activity references weak
 * see if the dialog is preserved on flip
 * 
 * Key notes:
 * 1. I am a RADO
 * 2. I have pointers to other RADO to which I am not a parent
 * 3. I have no child RADOs 
 */
public class MyLongTaskWithFragmentDialog3 
extends AsyncTask<String,Integer,Integer>
implements IRetainedADO, IFragmentDialogCallbacks
{
   public String tag = null;
   public static String PROGRESS_DIALOG_FRAGMENT_TAG_NAME="AsyncDialog";

   //To support multiple inheritance
   //Transfer all calls to the implementation
   private IRetainedADO retainedADOImpl;
   
   //To report back errors
   //Represents the BaseTester
   //This should track the activity status
   //and likely a RADO itself
   private IReportBack r;
   
   MyLongTaskWithFragmentDialog3(IRetainedADO parentRADO
         ,IReportBack inr
         , String inTag)
   {
      tag = inTag;
      retainedADOImpl = new RetainedADO(parentRADO, inTag, this);
      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
      showDialogFragment();
   }

   /*
    * I am going to start the asynctask
    * show the dialog fragment.
    * Name the fragment.
    */
   private void showDialogFragment()   {
      Activity act = this.getActivity();
      ProgressDialogFragment pdf = new ProgressDialogFragment();
      pdf.show(act.getFragmentManager(), this.PROGRESS_DIALOG_FRAGMENT_TAG_NAME);
   }
   
   //this can be null
   private ProgressDialogFragment getDialog()   {
      Activity act = getActivity();
      if (act == null)      {
         Log.d(tag, "activity is null. shouldnt be. returning a null dialog");
         return null;
      }
      //Good dialog
      return (ProgressDialogFragment)
          act.getFragmentManager()
             .findFragmentByTag(this.PROGRESS_DIALOG_FRAGMENT_TAG_NAME);
   }
   
    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 (isActivityReady())       {
          r.reportBack(tag, "Progress:" + i.toString());
          //set progress
          //wpd.get().setProgress(i);
          setProgressOnProgressDialog(i);
       }
       else     {
          Log.d(tag,"Activity is not ready! Progress update not displayed:" + i);
       }
    }

    private void setCallbacksOnProgressDialog()    {
       ProgressDialogFragment dialog = getDialog();
       if (dialog == null)       {
          Log.d(tag, "Dialog is not available to set callbacks");
          return;
       }
       dialog.setDialogFragmentCallbacks(this);
    }
    
    private void setProgressOnProgressDialog(int i)    {
       ProgressDialogFragment dialog = getDialog();
       if (dialog == null)       {
          Log.d(tag, "Dialog is not available to set progress");
          return;
       }
       dialog.setProgress(i);
    }
    private void closeProgressDialog()    {
       DialogFragment dialog = getDialog();
       if (dialog == null)       {
          Log.d(tag, "Sorry dialog fragment is null to close it!");
          return;
       }
       //Dismiss the dialog
       Log.d(tag,"Dialog is being dismissed");
       dialog.dismiss();
    }
    
    //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 (isUIReady())
      {
         Log.d(tag,"UI is ready and running. dismiss is pemissible");
         closeProgressDialog();
         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);
    }
    public void onCancel(DialogInterface d)
    {
        conditionalReportBack("Cancel Called");
       this.cancel(true);
    }
    private void conditionalReportBack(String message)
    {
       Log.d(tag,message);
       r.reportBack(tag,message);
    }
    
    //*****************************************************
    //Redirect work for multiple inheritance
    //*****************************************************
   public MonitoredActivityWithADOSupport getActivity() {
      // TODO Auto-generated method stub
      return retainedADOImpl.getActivity();
   }
   public void reset() {
      retainedADOImpl.reset();
   }
   public void attach(MonitoredActivityWithADOSupport act) {
      retainedADOImpl.attach(act);
      //set this object to respond to callbacks
      setCallbacksOnProgressDialog();
      
      //dismiss dialog if needed
      if (bDoneFlag == true)      {
         Log.d(tag,"On my start I notice I was done earlier");
         closeProgressDialog();
      }
   }
   public void releaseContracts() {
      retainedADOImpl.releaseContracts();
   }
   public boolean isActivityReady() {
      return retainedADOImpl.isActivityReady();
   }
   public void addChildRetainedADO(IRetainedADO childRetainedADO) {
      retainedADOImpl.addChildRetainedADO(childRetainedADO);
   }
   public boolean isUIReady() {
      return retainedADOImpl.isUIReady();
   }
   public boolean isConfigurationChanging()   {
      return retainedADOImpl.isConfigurationChanging();
   }
   /**
    * You may need to remove this worker ADO once its job is done
    * @param childRetainedADO
    */
   public void removeChildRetainedADO(IRetainedADO childRetainedADO)
   {
      retainedADOImpl.removeChildRetainedADO(childRetainedADO);
   }
   /**
    * Not sure when you need to call this
    */
   public void removeAllChildRetainedADOs()   {
      retainedADOImpl.removeAllChildRetainedADOs();
   }
   
   /**
    * To support Named ADOs. These are useful
    * for repeated menu commands.
    *  
    * @return The name of this ADO if available or null otherwise
    */
   public String getName()   {
      return retainedADOImpl.getName();
   }
   public void detachFromParent()   {
      retainedADOImpl.detachFromParent();
   }
   public void logStatus()   {
      retainedADOImpl.logStatus();
   }
   public void addChildRetainedADOOnly(IRetainedADO childRetainedADO)   {
      retainedADOImpl.addChildRetainedADOOnly(childRetainedADO);
   }
    //*****************************************************
    //DialogFragment callback methods
    //*****************************************************
   public void onCancel(DialogFragment df, DialogInterface di)    {
      Log.d(tag,"onCancel called");
   }

   public void onDismiss(DialogFragment df, DialogInterface di)    {
      // TODO Auto-generated method stub
      Log.d(tag,"onDismiss called");
      Log.d(tag,"Remove myself from my parent");
      detachFromParent();
   }
}//eof-class

satya - 11/25/2013 2:57:02 PM

IFragmentDialogCallbacks


public interface IFragmentDialogCallbacks 
{
   public void onCancel(DialogFragment df, DialogInterface di);
   public void onDismiss(DialogFragment df, DialogInterface di);
}

satya - 11/25/2013 3:00:31 PM

What you will accomplish at the end of this

  1. A long running asynctask
  2. A modal progress dialog
  3. Back is prevented
  4. Home is allowed
  5. Configuration change works
  6. Going home and back works midway of the task
  7. Going home and back works even if the task finishes in the interim
  8. All loose objects are gathered up and cleaned
  9. A model for using ADOs broadly if they are applicable

Of course you will need to tweak the ADOs so that they can be used with and without fragments in a uniform way