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!

20 comments:

  1. Hi Deep Shah,

    let me know their is any tool to convert Telerik Tags into general Asp Tags. Actually I got a Project which is built with Telerik Tags I want to remove the all Telerik tags and customize that project with out changing the UI.

    Please help me if you have any idea on that.

    ReplyDelete
  2. Hello Subbu,

    There is no silver bullet as they say. There is no tool that can convert all Telerik tags into asp tags. At least none that I am aware of.

    You will have do it step by step. Use asp tags with JQuery and you should be on the right course.

    Thanks

    Deep Shah

    ReplyDelete
  3. Hi Deep Shah,
    I want to change some views when the orientation changes for example i want to put text view from bottom of the screen to screen's right side when the orientation changes from portrait to landscape mode.How to achieve it?

    ReplyDelete
  4. Hello Bassu,

    Please refer my earlier post http://www.gitshah.com/2011/03/how-to-handle-screen-orientation.html where I explained how can you use separate layouts for landscape and portrait screen orientations.

    For e.g. If you activity uses a.xml as its layout. Place the a.xml in "layout" and "layout-land" folders. Then you can edit the a.xml in the "layout-land" folder as you need and correct layout file will be picked up when screen orientation changes.

    Thanks

    Deep Shah

    ReplyDelete
  5. What about simple activities which are only displaying texts?!

    In these cases it's not the big problem to use configChanges, is it?

    Could you explain more, why using configChanges will make my life more complicated?!

    I spoke with an android developer about this problem. And he meant following. If you don't have different layouts for landscape and portrait orientation, you can easy use configChanges.

    And here is the note from Android documentation:
    "If your application doesn't need to update resources during a specific configuration change and you have a performance limitation that requires you to avoid the Activity restart, then you can declare that your Activity handles the configuration change itself, which prevents the system from restarting your Activity."

    ReplyDelete
  6. Hello Mur Votema,

    If its a simple activity which simply shows text then why bother about configChanges its best to let the Android system handle those effectively.

    If you visit the link http://developer.android.com/guide/topics/manifest/activity-element.html#config

    "Note: Using this attribute should be avoided and used only as a last-resort. Please read Handling Runtime Changes for more information about how to properly handle a restart due to a configuration change."

    and

    http://developer.android.com/guide/topics/resources/runtime-changes.html

    it explicitly mentions that configChanges should be used only as the last resort.

    Hence, I would still go with the documentation and let Android system handle the screen orientation for me.

    Thanks

    Deep Shah

    ReplyDelete
  7. Hi,
    In our application we are using AsyncTask for server requests'. If a request is made then the ProgressDialog appears on the screen, but on orientation change it re-sends the request to server.

    Hence the resulting activity is displayed twice.

    I'm using showDialog and onCreateDialog for displaying ProgressDialog.

    Can you give me a solution asap?

    Thanks

    ReplyDelete
  8. Hello Sahana Uday,

    What you need to do is notify the Async Task that activity that started the Async task is no longer existent and cancel the Async process. Next when the activity starts up in landscape mode it should re-start the Async Task activity. Hence, in the onStop of the activity Async task should be stopped or cancelled and a new task should be created in the new instance of the activity.

    Thanks

    Deep Shah

    ReplyDelete
  9. BIG UP YOURSELF FOR DEEP SHAH!!!!!

    ReplyDelete
  10. excellent post !!!
    no doubts wat so ever

    ReplyDelete
  11. hi! i tryied to use this:

    @Override
    protected void onSaveInstanceState(final Bundle outState) {
    outState.putSerializable("someExpensiveObject", someExpensiveObject);
    }

    I used this to save my async task class but it gives some serializable errors....

    ReplyDelete
  12. Hello Bluemercury,

    You can only put serializable objects in the callback onSaveInstanceState.

    Its not a good idea to put the reference of the Async task into the Bundle. It would be a good idea to put enough information into the Bundle which will enable you to re-instantiate the Async task after the activity is recreated.

    Thanks

    Deep Shah

    ReplyDelete
  13. Hello Deep,

    When I try to run your code I get 3 errors. Can you please explain to me why this is. Thank you sir.

    Serializable cannot be resolved to a type HelloWebAppActivity.java /HelloWebApp/src/testdev/HelloWebApp line 65 Java Problem

    The method onSaveInstanceState(Bundle) of type HelloWebAppActivity.SomeActivity must override a superclass method HelloWebAppActivity.java /HelloWebApp/src/testdev/HelloWebApp line 56 Java Problem

    The method putSerializable(String, Serializable) in the type Bundle is not applicable for the arguments (String, HelloWebAppActivity.SomeExpensiveObject) HelloWebAppActivity.java /HelloWebApp/src/testdev/HelloWebApp line 57 Java Problem

    ReplyDelete
  14. Hi Deep !
    I have to different Layouts which i load for landscape n portrait orientation.
    all the data in the layout are parsed to the data coming from webservice. when user changes oreintation all data get refreshed and it call the webservice for data. i've used the Method 1. but nothing is happening. Am i missing something? please help.

    ReplyDelete
  15. This post is mostly lifted from the Android Developer's blog:

    http://developer.android.com/resources/articles/faster-screen-orientation-change.html

    ReplyDelete
  16. Nicely explained.. Exactly what i was looking for.. Thanks

    ReplyDelete
  17. Hello There,

    Of course it is. Android documentation is the mother of all knowledge base for Android world.

    ReplyDelete
  18. Hi Deep Shah, I having some problems with " android:configChanges="orientation|keyboardHidden" ".
    I'm developing an app with android:minSdkVersion="8" and android:targetSdkVersion="17".
    If I install the app on android 2.2 or 2.3 device it works good without no problem when I turn the device in portrait or landscape, but if I install it on a device with android 3.0 or higher if I turn the device the activity is recreated.
    Can you please help me?

    ReplyDelete

Have some Fun!