Tips on handling orientation changes in Android 2

Here are some tips on how to handle and test orientation changes in Android 2

This post applies to Android 2.x, since onRetainNonConfigurationInstance() is deprecated in Android 3.x.

In my previous post I showed one method I use to test orientation changes. The standard way of handling orientation change is to save the results of expensive operations
in onRetainNonConfiguration(), and then when the activity is recreated that data can be re-used.

From the Android developer documentation

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.

And here.

This function is called purely as an optimization, and you must not rely on it being called

This means that onRetainNonConfigurationChange() is supposed to be an optimization, so the activity should be able to recreate the data if required

Now here is a list some things that I will usually save in onRetainNonConfigurationChange().

Expensive operation results – just like the documention says. After all you don’t want your app to be re-doing things like database queries, network calls or complex
calculations every time the user flips the phone.

AsyncTask – there are some situations where a running AsyncTask will be saved, so that it can continue it’s processing after the orientation change (rather than starting the task again). Makes sense since one of reasons to use an AsyncTask is to handle long-running operations.

Intermediate data – I was using some fields in the activity to hold some (non-expensive) data, so I didn’t bother to save it because I thought it was trivial. The result was NullPointerExceptions because that data was required either in the onCreate() or later on, e.g. onDialogCreate()when I wanted to display that data in a message. So you can either save this data, re-create it in onCreate() or at least do a null check before using it.

Maps – if the activity displays a map (e.g. using a MapActivity), then it might be more user friendly to save things like the zoom level, satellite view option, and the centre point of the map.  This is so that the user has a similar map view after the orientation changes.

Some other tips not related to saving state

Dialogs– if you are doing your own dialog handling, instead of letting the activity do it by using onCreateDialog() and showDialog(), then you need to handle the situation
where the dialog is displayed when the orientation change occurs. I generally let the activity do the hard lifting, except maybe in cases where the life cycle of the dialog doesn’t
match the activity, e.g. a progress dialog for an AsyncTask.
Generally in my unit tests, I will follow each dialog display with an orientation change to check that afterwards:
1. the dialog is still displayed
2. if the dialog shows data, that the data hasn’t changed

Layouts – if you have alternate layouts for portrait and landscape, goes without saying that you need to test both! This includes things like testing the order of focus for the views, etc. since the views may be in different positions in the different layouts.
This is the reason that in my previous post: Test Android orientation changes with Robotium I ran the test methods multiple time starting in both portrait and landscape mode.

Some more testing tips

  • Some versions of the emulator may have bugs that show up when your test invokes an orientation change, e.g. the 2.3 version will allow your app to change normally from portrait to landscape, but then it has problems when you try to change back to portrait again. The test will still run OK, the will not look right in the emulator window.
  • If you are using the emulator and have some Robotium or instrumentation test code to click on an item in a list by index, e.g. solo.clickInList(1). Then the orientation change may cause some test failures when going into landscape mode.
    This is because fewer items being displayed in the list in landscape than in portrait mode, of course this is only a problem is there are more items in the list than can be displayed in the view or ‘page’.
    See if you can use solo.clickOnText() instead with the option to allow scrolling if required.

If you can think of anything else that might be useful, please let me know!

Test Android orientation changes with Robotium

One of the most painful things I found while learning Android development was handling orientation changes (who thought flipping your phone would cause so much trouble).

This is because the activities are being destroyed and recreated every time the phone is changing between portrait and  landscape mode. Here I’m assuming that it is being handled ‘properly’ by saving state data (e.g. in onRetainNonConfigurationInstance() ) and then reloading it when the activity is created.

After this saving and reloading code is in place, then it needs to be unit tested, and here I’ve found Robotium to be very useful in running instrumentation tests with orientation changes.

An example

This is just one way that I’ve found useful, here is an example – take this simple test to simulate the user entering some text and clicking some buttons:

public void testValidateText()
{
  EditText targetText = solo.getEditText(0);
  solo.enterText(targetText, "hello");

  // click on button to bring up dialog
  solo.clickOnButton("Validate");

  // wait to dialog to pop up
  getInstrumentation().waitForIdleSync();

  // click on button in dialog
  solo.clickOnButton("OK");
}

For the sake of simplicity, I’ve left out the Robotium setup code and the asserts (which would normally be the point of the testing!).

Ring the (orientation) changes

In the next version of this test, I will:

  • rename the test method to something that doesn’t start with ‘test’, because Android used JUnit 3, this will stop this method from being run in the test case
  • add some parameters to the test method as flags for doing orientation change
  • add to code in the method to change the orientation
  • write some wrapper methods to call our test method
public void testValidateText()
{
  doTestValidateText(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, false);
}

public void testValidateTextWithOrientationChange()
{
  doTestValidateText(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, true);
}

public void testValidateTextInLandscapeWithOrientationChange()
{
  solo.setActivityOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  doTestValidateText(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, true);
}

public void doTestValidateText(int currentOrientation, boolean changeOrientation)
{
  int orientation = currentOrientation;

  EditText targetText = solo.getEditText(0);
  solo.enterText(targetText, "hello");

  orientation = changeOrientation(solo, changeOrientation, orientation);

  // click on button to bring up dialog
  solo.clickOnButton("Validate");

  orientation = changeOrientation(solo, changeOrientation, orientation);

  // wait to dialog to pop up
  getInstrumentation().waitForIdleSync();

  orientation = changeOrientation(solo, changeOrientation, orientation);

  // click on button in dialog
  solo.clickOnButton("OK");
}

So now when the test case is run, the original test method will be run in 3 ways:

  • testValidateText() will run the test as before
  • testValidateTextWithOrientationChange() will run the test, but after every user action will change the orientation with the line
    orientation = changeOrientation(solo, changeOrientation, orientation);
  • testValidateTextInLandscapeWithOrientationChange() will also run the test with orientation changes, but starting off in landscape mode

Here is the code for the orientation change.


public int getNextOrientation(int currentOrientation)
{
  if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
  {
    return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
  }
  else
  {
    return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
  }
}

public int changeOrientation(Solo solo, int currentOrientation)
{
  int nextOrientation = getNextOrientation(currentOrientation);
  solo.setActivityOrientation(nextOrientation);

  return nextOrientation;
}

public int changeOrientation(Solo solo, boolean changeOrientation, int orientation)
{
  int newOrientation = orientation;
  if (changeOrientation)
  {
    newOrientation = changeOrientation(solo, orientation);
  }
  return newOrientation;
}

So basically the wrapper methods just invoke the original test method with a flag for the orientation that it starts in, and whether to do orientation changes during the test or not.

In the next post, I will list some problems in my code that I exposed with this testing, and other issues with coding and testing for orientation changes.