By default, when the device orientation changes, Android destroys the active activity and creates a new one. This lets you load a different layout for the new orientation. Unfortunately, this also means, you will need to reload any data from the server or database. Prior to Android 3.0 (Honeycomb), Android provided a way to pass data from the old activity to the new one through the onRetainNonConfigurationInstance()/getLastNonConfigurationInstance() methods. This approach has since been deprecated. The Fragment API provides a better option. The process is still fraught with danger, if your fragment does any advanced work like background tasks and progress dialog.
In this article, we will explore options to deal with orientation change. For majority of the application, the choices can be surprisingly simple.
Option 0 – Do Nothing
As I said, if you do nothing, the activity will be destroyed after an orientation change. A new one will be created. This option is only valid if the initialization process for the activity (from onCreate()) is trivial and very quick. If onCreate() is time consuming or you start asynchronous tasks from there, things can become very slow and buggy when orientation changes. In those situations, consider the following options.
Option 1 – Fix Orientation
Many activities work best in one orientation. Other orientations may be harmful to user experience. Sample scenarios are:
- A movie player should use the landscape orientation. Portrait orientation is just too narrow.
- An activity showing a list of customers work best in portrait mode. Landscape mode shows fewer customers.
In situations like these, you should fix the orientation of the activity. You do that by setting the screenOrientation attribute in the manifest file.
<activity android_label="Customer List" android_name="CustomerListActivity" android:screenOrientation="portrait"> </activity>
Option 2 – Recycle the Activity
For majority of applications, simply resizing the views to fit into the new orientation is sufficient. The layout managers are pretty good at resizing views. All you need to do is find a way to not destroy the activity after an orientation change and simply have the views resized. This can be easily done by setting the configChanges attribute. For example:
<activity android_label="Address Book" android_name=".MainActivity" android:configChanges="orientation|screenSize"> </activity>
Note: As of Android 3.0, you will need to add the screenSize flag. This wasn’t necessary prior to that.
This option is most preferred if you have a complicated initialization process for an activity or fragment within an activity. For example, they can run an asynchronous task and show a progress dialog when the orientation changes. Everything will continue to function normally and uninterrupted.
Option 3 – Retain Fragment
This option is appropriate when these conditions exist:
- The application must show an orientation specific layout. To do this, the old activity has to be destroyed. The new activity will load a different layout. And,
- The initialization process is time consuming or does asynchronous work and should not be redone for the new activity.
This is the most complex scenario. Very few applications will fall into this category. So, choose this option carefully and if you must.
Starting from Android 3.0, the correct approach to handle this problem is through fragment. Instead of managing the views and initializing them directly from the activity, do those things from a fragment. When orientation changes, the activity instance is destroyed, but, the fragment object can be recycled. This is a two step process:
- Flag the fragment object as retainable (recyclable). After that, when orientation changes, the fragment is detached from the old activity and attached to the new one. Note: The views managed by the fragment get destroyed and the fragment must reload the layout from onCreateView.
- Save all state data in the member variables of the fragment object. Since, the fragment object survives an orientation change, all state data survives with it. State data includes and data loaded from web service or database, AsyncTask and ListAdapter. This gives you a lot of flexibility. For example, an AsyncTask started by the fragment can continue after an orientation change.
Flag the fragment instance as retainable from onCreate() of the fragment class.
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); }
From the onCreate() view method, load a layout and recreate the view hierarchy as usual.
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View v = inflater.inflate(R.layout.addressbook, container); return v; }
The onActivityCreated() is the complex one. It needs to load data only if it hasn’t been already loaded. It needs to populate the views in all cases. That’s because, the views are recreated after orientation change.
In the example below, the fragment uses a background task to load data. We start the task only if hasn’t been started yet. If the task has already finished, we simply populate the views.
public class CustomerListFragment extends Fragment { CustomerListTask mTask; ArrayList<Customer> mCustomerList; public View onCreateView(…){…} public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (mTask != null) { if (mTask.getStatus() == Status.FINISHED) { //Data is already loaded. Show it showCustomerList(mCustomerList); } //Task still running return; } //Start work in background mTask = new CustomerListTask(); mTask.execute(); } class CustomerListTask extends AsyncTask<Void, Void, ArrayList<Customer>> { protected ArrayList<Customer> doInBackground(Void... params) { mCustomerList = fetchCustomerList(); return mCustomerList; } protected void onPostExecute(ArrayList<Customer> list) { showCustomerList(list); } } }