How to handle configuration change in an activity using Fragments in android?

By | October 6, 2013

Here is a simple example in which we will save or retain the state of the progress using fragments.

Note : We have handle a configuration by another way – by ignoring the change i.e using this property in the activity tag
android:configChanges=””
By using this android ignores the configuration change and will never try to create a new instance of the activity.
But Google discourages this practice.

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.

retain state

retain state

Let’s go to the code and see how it is done.

Now we will see how to do that.

At first I will show you the layout which simply contains a textview that show the progress.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_alignParentTop="true"
        android:text="Handling Runtime configuration changes by saving state using Fragments"
        android:textColor="@android:color/black"
        android:textSize="20sp" />

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" >

        <TextView
            android:id="@+id/tv_pro"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:textColor="@android:color/holo_blue_bright"
            android:textSize="40sp" />
    </FrameLayout>

</RelativeLayout>

Now we will see the code for the activity.

package com.example.retaininstanceusingfragmentsexample;

import android.app.Activity;
import android.app.FragmentManager;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity implements
		TaskFragment.TaskCallbacks {

	private TaskFragment mTaskFragment;
	TextView tvPro = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		tvPro = (TextView) findViewById(R.id.tv_pro);

		FragmentManager fm = getFragmentManager();
		mTaskFragment = (TaskFragment) fm.findFragmentByTag("task");

		// If the Fragment is not null, then it is currently being
		// retained when a configuration change occurs.
		if (mTaskFragment == null) {
			mTaskFragment = new TaskFragment();
			fm.beginTransaction().add(mTaskFragment, "task").commit();
		}

		// TODO: initialize views, restore saved state, etc.
	}

	@Override
	public void onPreExecute() {
	}

	@Override
	public void onProgressUpdate(int percent) {
		tvPro.setText(percent + "%");
	}

	@Override
	public void onCancelled() {
	}

	@Override
	public void onPostExecute() {
	}
}

Now the Fragments that handles the retained state.

package com.example.retaininstanceusingfragmentsexample;

import android.app.Activity;
import android.app.Fragment;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;

/**
 * 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 update the UI
	 */
	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(500);
				// call to onProgressUpdate
				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();
			}
		}
	}
}

After running this application, try to rotate the device and see how the fragment retains the state of the progress.

You can download the complete source code of this post from here.

Leave a Reply

Your email address will not be published. Required fields are marked *