Handling Orientation Changes in Android


Sometimes handling the orientation changes for your Activity, Fragment or AsyncTasks becomes most frustrating things to deal. If orientation changes is not handle properly then it results unexpected behavior of the application.

When such changes occurs, Android restarts the running Activity means it destroy and again created.


Why?

When configurations changed during runtime (such as screen orientation, keyboard availability, and language), Android usually destroys application’s existing Activity or Fragment and recreate it.

Android does this so that application can reload its resources based on the new configuration. The restart behavior helps application to adapt new configurations by automatically reloading the application with alternative resources that match the new device configuration.

Proper handling of orientation changes makes rich user experience (not lost UI state) for the application and it also avoiding memory leaks.


How to handle?

To handle these configuration changes, Android provides callbacks to save your application state before destroying either Activity or Fragment. In the same it also provides to restore the application state when it is recreating them.

There are a different options to handle the orientation changes:

1.    Lock screen orientation
2.    Prevent Activity to recreated
3.    Save basic state
4.    Save complex objects


Lock screen orientation

To lock the screen orientation change of any screen (activity) of your android application makes your activity display only in one mode i.e. either Landscape or Portrait. This is the simplest way to handle screen orientation but not generally recommended.

For this you need to add below line in your projects AndroidManifest.xml. Add the below line along with your activity entry in the AndroidManifest file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.test">

  ...
 
    <application
        android:name="com.example.test.TestApplication"
        android:label="@string/app_name">

        <activity
            android:name="com.example.test.activity.MainActivity"
            android:screenOrientation="portrait"/>

        <activity
            android:name="com.example.test.activity.HomeActivity"
            android:screenOrientation="landscape"/>
  ...
                             
    </application>


</manifest>


Prevent Activity to recreated

Another most common solution to dealing with orientation changes by setting the android:configChanges flag on your Activity in AndroidManifest.xml. Using this attribute your Activities won’t be recreated and all your views and data will still be there after orientation change.

<activity
    android:name="com.example.test.activity.MainActivity"
    android:configChanges="orientation|screenSize "/>

This attribute informs to Android system that you are going to handle orientation and screenSize changes for this Activity. So instead of destroying and recreating your Activity, Android will just rotate the screen and invoke one of the lifecycle callback method which is onConfigurationChanged(Configuration).

Note: In case you want to do something like display different layout then you have to implement onConfigurationChanged(Configuration) method and before inflating the new layout, you would require to manually discard the old layout. To use android:configChanges attribute is also not recommended by Android.


Save basic state

This is the most common situation to save the basic data of your Activity or Fragment during orientation change. You can save Primitive data such as String, Boolean, Integers or Parcelable objects in a Bundle during the orientation change and read the same data when Activity recreated.

Saving and restoring the data works using two Activity lifecycle methods called onSaveInstanceState() and onRestoreInstanceState().

To save the state information override onSaveInstanceState() method and add key-value pairs to the Bundle object that is saved in the event that your activity is destroyed unexpectedly. This method gets called before onStop().

To recover your saved state from the Bundle override onRestoreInstanceState() method. This is called after onStart() and before onResume().

public class MainActivity extends Activity{

private static final String SELECTED_ITEM_POSITION = "ItemPosition";
private int mPosition;
  
   @Override
    protected void onSaveInstanceState(final Bundle outState) {
        super.onSaveInstanceState(outState);
       
        // Save the state of item position
         outState.putInt(SELECTED_ITEM_POSITION, mPosition);  
    }

    @Override
    protected void onRestoreInstanceState(final Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
       
        // Read the state of item position
        mPosition = savedInstanceState.gettInt(SELECTED_ITEM_POSITION);
    }
}


Save complex objects

Deprecated: Override onRetainNonConfigurationInstance() and getLastNonConfigurationInstance()

Prior to Honeycomb’s release, the recommended means of transferring active objects across Activity instances was to override the onRetainNonConfigurationInstance() and getLastNonConfigurationInstance() methods.  After API level 13 these methods have been deprecated in favor of the more Fragment’s setRetainInstance(boolean) capability, which provides a much cleaner and modular means of retaining objects during configuration changes.


Recommended: Manage complex Object inside a Retained Fragment

Since Fragments introduced from API level 11, the recommended means of retaining active complex objects across Activity instances is to wrap and manage them inside of a retained “worker” Fragment.

In general Fragments are destroyed and recreated along with their parent Activity when a configuration change occurs.

Calling setRetainInstance(true) inside Fragment protect from destroy and recreate and retain the current instance of the fragment when the activity is recreated.

To retain the objects state in a fragment during a runtime configuration change you have to do following steps:

1.    Extend the Fragment class and declare references to your stateful objects.
2.    Call setRetainInstance(boolean) when the fragment is created.
3.    Add the fragment to your activity.
4.    Use FragmentManager to retrieve the fragment when the activity is restarted.
Your worker Fragment would look like-

public class WorkerFragment extends Fragment {

    // data object we want to retain
    private DataObject data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(DataObject data) {
        this.data = data;
    }

    public DataObject getData() {
        return data;
    }
}

Now you can obtain the data object from the Fragment when the Activity starts again during runtime configuration changes like:

public class MainActivity extends Activity {

    private static final String TAG_WORKER_FRAGMENT = "WorkerFragment";

    private WorkerFragment mWorkerFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        mWorkerFragment = (WorkerFragment) fm.findFragmentByTag(TAG_WORKER_FRAGMENT);

        // create the fragment and data the first time
        if (mWorkerFragment == null) {
            // add the fragment
            mWorkerFragment = new WorkerFragment();
            fm.beginTransaction().add(mWorkerFragment, TAG_RETAINED_FRAGMENT).commit();
            // load data from a data source or perform any calculation
            mWorkerFragment.setData(loadMyData());
        }

        // the data is available in mRetainedFragment.getData() even after
        // subsequent configuration change restarts.
        ...
    }
}

In order to proactively remove the retained worker fragment when you no longer need it, you may check for isFinishing() in onPause() in the activity.


Handle AsyncTask

When AsyncTask is running without changing the screen orientation then it will start and finish its work normally. But problems begin to appear when the device orientation is changed while the AsyncTask is in the middle of the work.

The application will crash or java.lang.IllegalArgumentException i.e. View not attached to window manager will be thrown or Activity has leaked window.
To resolve this problem one option is to use IntentService along with BroadCastReceiver to deliver result.

Another option is to run the AsyncTask inside worker Fragment. As explained above using fragments is the cleanest way to handle configuration changes because Fragment has the ability to retain their instances simply by calling setRetainInstance(true) in one of its callback methods.


Conclusion

Hopefully with this article you are familiar with different ways which will help you to handle orientation changes. Use of worker Fragments as a retained Fragments seems hard to understand and looks like a lot of extra work at first but it will also make the user’s experience with your application much better. You can also refer official Android document for more details.

To find more interesting topics on Software development follow me at https://medium.com/@ankit.sinhal.

Twitter: https://twitter.com/ankitsinhal

Comments

Post a Comment

Popular posts from this blog

Android Performance: Avoid using ENUM on Android

Android O: Impact On Running Apps And Developer Viewpoint

Smart way to update RecyclerView using DiffUtil