Monday, March 28, 2011

How to handle Screen Orientation changes in Android - Part - 2

Adapting the GUI when screen orientation changes is extremely important for a good user experience experience.  We saw in my previous post, how Android framework does an excellent job at adapting the GUI when screen orientation changes.

Android *destroys* the current activity and *re-creates* the same activity all over again.  

We also saw that in certain situations this architectural choice could lead to bad user experience.

For example

In the above example think about the user experience when, he/she changes the screen orientation while viewing SomeActivity
  • User has waited 5 seconds for the activity to load completely
  • He/She changes the screen orientation
  • Current activity is destroyed
  • New activity is re-created
  • User has to wait for 5 more seconds for the activity to be loaded completely with the new screen orientation.
While the default behavior of destroying and recreating the activity is powerful, its sometimes confusing for new Android developers, who wonder why their activity is destroyed and recreated.

Moreover, if we do not handle this correctly, user experience goes for a toss.  He has to wait 5 extra seconds for no apparent reason.

Because of this, some developers decide to handle the configuration changes (like screen orientation changes) themselves.  This is done by adding the "configChanges" attribute to the Activity declaration in AndroidManifest.xml

The "configChanges" attribute tells Android Platform that, some of the config changes will be handled by the activity by themselves. Platform does not need to do any special handling for these.

The above code tells Android platform that Screen orientation changes will be handled by the activity on its own. In this case Android platform will not destroy the current activity and re-create it.

In general, this is a short-term solution that will only complicate developers lives later.

The Android Platforms automatic resource handling is a very efficient and easy way to adapt an application's user interface to various devices and devices configurations. Hence, its *not advisable* to use the above config value for handling screen orientation!

So what is the alternate?

How do they do it?

Android platform provides two elegant ways to handle this situation.  Lets look at them

Method #1:

The Activity class has a special method called onRetainNonConfigurationInstance()

This method can be used to pass any arbitrary object to your *future self*.  

What does that sentence mean?  

As the name suggests, we can retain an instance of a non configuration object using this method.  The return type of onRetainNonConfigurationInstance method is Object.  The value that you return from this method gets passed to the new activity instance that will be created because of the screen orientation change.

Android Platform is smart enough to call this method only when needed.  For example, in our case we would return an instance of SomeExpensiveObject class that was created earlier.  This way the new instance of SomeActivity will have access to an instance of SomeExpensiveObject automatically and it does not need to create the instance again.

Code is better than 1000 words, lets look at how this is done.

Question arises, how do we get hold of SomeExpensiveObject instance in onCreate method of the new instance of SomeActivity?

To get access to the instance of non configuration object that we saved earlier, we have to invoke the  getLastNonConfigurationInstance() method.  In our example, to get the instance of SomeExpensiveObject class which we retained earlier, we have to invoke the getLastNonConfigurationInstance method. Lets look at how the updated code would look like.

Have a look at the updated code. We first try to get the instance of SomeExpensiveObject using the getLastNonConfigurationInstance() method. If that instance is null then only we create a new instance of SomeExpensiveObject class.


Think of the user experience when, he/she changes the screen orientation while viewing the SomeActivity with updated code.
  • User has waited 5 seconds for the activity to load
  • He/She changes the screen orientation
  • Android platform calls onRetainNonConfigurationInstance()
  • Current activity is destroyed
  • New activity is recreated
  • We try to get the instance of SomeExpensiveObject using the getLastNonConfigurationInstance()
  • Since we will find the instance of SomeExpensiveObject we do not create another instance and hence user does not need to wait for 5 more seconds!
  • End result, user is happy.  Everyone loves to have happy users!


Disclaimer: 

Be very careful with the object you pass through onRetainNonConfigurationChange(), though. 

If the object you pass is for some reason tied to the Activity or Context, you will leak all the views and resources of the activity. This means you should never pass a View, a Drawable, an Adapter, etc.  Finally, remember that onRetainNonConfigurationChange() should be used only to retain data that is expensive to load. Otherwise, keep it simple and let Android do everything.

Method #2:

Android provides another elegant way of achieving this.  To achieve this, we have to override a method called onSaveInstanceState().  Android platform allows the users to save any instance state.  Instance state can be saved in the Bundle.  Bundle is passed as argument to the onSaveInstanceState method.

Android calls onSaveInstanceState before pausing/destroying the activity.

This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state.  This gives us an opportunity to save any instance state we want in the Bundle.

In our case we are putting the SomeExpensiveObject instance in the Bundle.  For this to work we have to mark SomeExpensiveObject as Serializable.  We can also put primitives in the Bundle.

When Android recreates the activity we can get back the saved instance state as follows

As we can see, we can load the saved instance state from the Bundle passed as argument to the onCreate method. We can also load the saved instance state in "onRestoreInstanceState" method. But I will leave that for the readers to figure out.

We are now creating the instance of SomeExpensiveObject only if we are not able to get the instance from the saved state.

Think of the user experience when, he/she changes the screen orientation while viewing the SomeActivity with updated code.
  • User has waited 5 seconds for the activity to load
  • He/She changes the screen orientation
  • Android platform calls onSaveInstanceState()
  • Current activity is destroyed
  • New activity is recreated
  • We try to get the instance of SomeExpensiveObject using the Bundle argument passed to the onCreate method
  • Since we will find the instance of SomeExpensiveObject, we do not create another instance and hence user does not need to wait for 5 more seconds!
  • End result.  Users are happy!
Both these methods are the recommended ways of saving the instance state and getting it back.  Which one to use, is a choice left to the developers to decide.

Android is really a very powerful mobile development platform, but as Spider-Man's uncle Ben once said

With great power comes great responsibilities!

Use the Android platform in the correct way and you will enjoy your time with it!
Have some Fun!