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
i got good tutorial Android Online Training
ReplyDelete