Friday, August 2, 2013

Saving and Retrieving android fragment state OnSaveInstanceState



What methods of Fragment are get called when configuration changes ?


One difference between a fragment and an activity is that there is no onRestoreInstanceState( ) method to retrieve and restore the state in a fragment.  Instead, the fragment’s on onCreate( ), onCreateView( ) and onActivityCreated( ) lifecycle methods all get passed the Bundle object.  You can use these methods and the passed in Bundle containing the fragment’s saved state to restore any state you need.  Keep in mind, the Bundle passed to these methods will be null unless the system is recreating the fragment (i.e. this is not the first time the methods are called on the fragment).  So when trying to restore state, put a null check in place to before accessing the Bundle.

Just like an activity, a fragment will automatically save the data of any fragment View component in the Bundle by the View component’s id. And just like in the activity, if you do implement the onSaveInstanceState( ) method make sure you add calls to the super.onSaveInstanceState( ) methods so as to retain this automatic save of View data feature.





image




When the device is rotated (portrait to landscape or vice versa), the device changes its runtime configuration – causing a destroy and recreation of the activities and fragments.  Other configuration changes include the change of a language, addition or removal of an input device (such as a keyboard), or when a device changes dock status (when a device is plugged into/unplugged from a car or desk dock).

 How to retain the fragment instance during configuration changes? when setRetainInstance(true) is called, what would be the fragment lifecycle methods?


On the Fragment class is a pair of get/set methods called setRetainInstance( ) and getRetainInstance( ).  The setter takes a boolean and the getter, obviously, returns a boolean.  The boolean is an indication of whether a fragment instance should be retained across any associated activity’s re-creation during such actions as a configuration change.  In other words, a fragment instance does not have to be completely destroyed and recreated when an activity is destroyed and recreated.  If a fragment is told to retain its instance with a call to setRetainInstance(true), then the fragment lifecycle is altered such that the fragment’s onDestroy( ) and onCreate( ) methods will not be called and the existing instance is associated to the new activity instance.

image

onCreate and onDestroy would not be called .we should save anything in the onsavedinstance(Bundle bundle) bundle  and retrieved in onActivityCreated() with null check.


On the orientation change of a device, for example, the existing instance of a fragment is provided to the new instance of the associated activity when it gets recreated.

Wednesday, July 31, 2013

Handle Orientation with Fragments in android

Handling Configuration Changes with Fragments

This post addresses a common question: 

what is the best way to retain active objects—such as running Threads, Sockets, and AsyncTasks—across device configuration changes?

To answer this question, we will first discuss some of the common difficulties developers face when using long-running background tasks in conjunction with the Activity lifecycle. Then, we will describe the flaws of two common approaches to solving the problem. Finally, we will conclude with sample code illustrating the recommended solution, which uses retained Fragments to achieve our goal.

Configuration Changes & Background Tasks


One problem with configuration changes and the destroy-and-create cycle that Activitys go through as a result stems from the fact that these events are unpredictable and may occur at any time. Concurrent background tasks only add to this problem. Assume, for example, that an Activity starts an AsyncTask and soon after the user rotates the screen, causing the Activity to be destroyed and recreated. When the AsyncTaskeventually finishes its work, it will incorrectly report its results back to the old Activity instance, completely unaware that a new Activity has been created. As if this wasn't already an issue, the new Activity instance might waste valuable resources by firing up the background workagain, unaware that the old AsyncTask is still running. For these reasons, it is vital that we correctly and efficiently retain active objects across Activity instances when configuration changes occur.

Bad Practice: Retain the Activity


Perhaps the hackiest and most widely abused workaround is to disable the default destroy-and-recreate behavior by setting theandroid:configChanges attribute in your Android manifest. The apparent simplicity of this approach makes it extremely attractive to developers;Google engineers, however, discourage its use. The primary concern is that it requires you to handle device configuration changes manually in code. Handling configuration changes requires you to take many additional steps to ensure that each and every string, layout, drawable, dimension, etc. remains in sync with the device's current configuration, and if you aren't careful, you're application can easily have a whole series of resource-specific bugs as a result.
Another reason why Google discourages its use is because many developers incorrectly assume that setting android:configChanges="orientation"(for example) will magically protect their application from unpredictable scenarios in which the underlying Activity will be destroyed and recreated. This is not the case. Configuration changes can occur for a number of reasons—not just screen orientation changes. Inserting your device into a display dock, changing the default language, and modifying the device's default font scaling factor are just three examples of events that can trigger a device configuration change, all of which signal the system to destroy and recreate all currently running Activitys the next time they are resumed. As a result, setting the android:configChanges attribute is generally not good practice.


Deprecated: Override onRetainNonConfigurationInstance()


Prior to Honeycomb's release, the recommended means of transferring active objects across Activity instances was to override theonRetainNonConfigurationInstance() and getLastNonConfigurationInstance() methods. Using this approach, transferring an active object across Activity instances was merely a matter of returning the active object in onRetainNonConfigurationInstance() and retrieving it ingetLastNonConfigurationInstance(). As of API 13, these methods have been deprecated in favor of the more Fragment'ssetRetainInstance(boolean) capability, which provides a much cleaner and modular means of retaining objects during configuration changes. We discuss this Fragment-based approach in the next section.


Recommended: Manage the Object Inside a Retained Fragment


Ever since the introduction of Fragments in Android 3.0, the recommended means of retaining active objects across Activity instances is to wrap and manage them inside of a retained "worker" Fragment. By default, Fragments are destroyed and recreated along with their parent Activitys when a configuration change occurs. Calling Fragment#setRetainInstance(true) allows us to bypass this destroy-and-recreate cycle, signaling the system to retain the current instance of the fragment when the activity is recreated. As we will see, this will prove to be extremely useful with Fragments that hold objects like running Threads, AsyncTasks, Sockets, etc.
The sample code below serves as a basic example of how to retain an AsyncTask across a configuration change using retained Fragments. The code guarantees that progress updates and results are delivered back to the currently displayed Activity instance and ensures that we never accidentally leak an AsyncTask during a configuration change. The design consists of two classes, a MainActivity...
 * This Activity displays the screen's UI, creates a TaskFragment
 * to manage the task, and receives progress updates and results
 * from the TaskFragment when they occur.
 */
public class MainActivity extends Activity implements TaskFragment.TaskCallbacks {
 
  private TaskFragment mTaskFragment;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
 
    FragmentManager fm = getFragmentManager();
    mTaskFragment = (TaskFragment) fm.findFragmentByTag("task");
 
    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (mTaskFragment == null) {
      mTaskFragment = new TaskFragment();
      fm.beginTransaction().add(mTaskFragment, "task").commit();
    }
 
    // TODO: initialize views, restore saved state, etc.
  }
 
  // The four methods below are called by the TaskFragment when new
  // progress updates or results are available. The MainActivity
  // should respond by updating its UI to indicate the change.
 
  @Override
  public void onPreExecute() { ... }
 
  @Override
  public void onProgressUpdate(int percent) { ... }
 
  @Override
  public void onCancelled() { ... }
 
  @Override
  public void onPostExecute() { ... }
}

...and a TaskFragment...

/**
 * This Fragment manages a single background task and retains
 * itself across configuration changes.
 */
public class TaskFragment extends Fragment {
 
  /**
   * Callback interface through which the fragment will report the
   * task's progress and results back to the Activity.
   */
  static interface TaskCallbacks {
    void onPreExecute();
    void onProgressUpdate(int percent);
    void onCancelled();
    void onPostExecute();
  }
 
  private TaskCallbacks mCallbacks;
  private DummyTask mTask;
 
  /**
   * Hold a reference to the parent Activity so we can report the
   * task's current progress and results. The Android framework
   * will pass us a reference to the newly created Activity after
   * each configuration change.
   */
  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    mCallbacks = (TaskCallbacks) activity;
  }
 
  /**
   * This method will only be called once when the retained
   * Fragment is first created.
   */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
 
    // Create and execute the background task.
    mTask = new DummyTask();
    mTask.execute();
  }
 
  /**
   * Set the callback to null so we don't accidentally leak the
   * Activity instance.
   */
  @Override
  public void onDetach() {
    super.onDetach();
    mCallbacks = null;
  }
 
  /**
   * A dummy task that performs some (dumb) background work and
   * proxies progress updates and results back to the Activity.
   *
   * Note that we need to check if the callbacks are null in each
   * method in case they are invoked after the Activity's and
   * Fragment's onDestroy() method have been called.
   */
  private class DummyTask extends AsyncTask<Void, Integer, Void> {
 
    @Override
    protected void onPreExecute() {
      if (mCallbacks != null) {
        mCallbacks.onPreExecute();
      }
    }
 
    /**
     * Note that we do NOT call the callback object's methods
     * directly from the background thread, as this could result
     * in a race condition.
     */
    @Override
    protected Void doInBackground(Void... ignore) {
      for (int i = 0; !isCancelled() && i < 100; i++) {
        SystemClock.sleep(100);
        publishProgress(i);
      }
      return null;
    }
 
    @Override
    protected void onProgressUpdate(Integer... percent) {
      if (mCallbacks != null) {
        mCallbacks.onProgressUpdate(percent[0]);
      }
    }
 
    @Override
    protected void onCancelled() {
      if (mCallbacks != null) {
        mCallbacks.onCancelled();
      }
    }
 
    @Override
    protected void onPostExecute(Void ignore) {
      if (mCallbacks != null) {
        mCallbacks.onPostExecute();
      }
    }
  }
}


Flow of Events


When the MainActivity starts up for the first time, it instantiates and adds the TaskFragment to the Activity's state. The TaskFragment creates and executes a AsyncTask and proxies progress updates and results back to the MainActivity via the TaskCallbacks interface. When a configuration change occurs, the MainActivity goes through its normal lifecycle events, and once created the new Activity instance is passed to theonAttach(Activity) method, thus ensuring that the TaskFragment will always hold a reference to the currently displayed Activity instance even after the configuration change. The resulting design is both simple and reliable; the application framework will handle re-assigning Activity instances as they are torn down and recreated, and the TaskFragment and its AsyncTask never need to worry about the unpredictable occurrence of a configuration change.


Conclusion


Synchronizing background tasks with the Activity lifecycle can be tricky and configuration changes will only add to the confusion. Fortunately, retained Fragments make handling these events very easy by consistently maintaining a reference to its parent Activity, even after being destroyed and recreated.
A sample application illustrating how to correctly use retained Fragments to achieve this effect is available for download on the Play Store. The source code is available on GitHub. Download it, import it into Eclipse, and modify it all you want!