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.

TestDataCaptureJ – Capture data for your unit tests

When you are writing unit tests or doing Test Driven Development (TDD) , one of tasks you may need to do is to get some data to run your tests with. If the data that you need is fairly simple then there are various options, such as manually creating the data classes, create some mocks, stick some data in a database, etc.

However if you need a large amount of complex data, then these options can become very time consuming. This was the situation that I’ve come across myself in various projects, in particular where I need to test a the checkout stage in a online shop using data from the basket. The development environment was running on a snapshot of the production database (therefore ‘real’ data) and for the test scenarios I need the data from the shopping basket with various combinations of basket items. However the basket items were quite complex: the basket item class had about 35 fields, some of which were other objects each containing maybe 5 – 15 fields themselves.

To get all the test data I needed to test all the scenarios, I created a tool which evolved into the TestDataCaptureJ open source project that is hosted on GitHub. This is a Java development tool that can be used in java (web) applications to capture data as you run through the test scenarios. The data is logged to file, but in a format that you can just cut and paste into your unit tests (or better still, into classes that are used by your unit tests).

As an example, in the tutorial I used the jpetstore sample app from the Spring Framework. This just meant configuring the jpetstore web app to run with TestDataCaptureJ, and then going through the checkout process with some items in the basket. Then I used the generated log in some unit tests that I wrote to test part of the checkout process. This is an example of the code that was generated:

public org.springframework.samples.jpetstore.domain.Account createParam1Account_org_springframework_samples_jpetstore_domain_Order_initOrder() {

org.springframework.samples.jpetstore.domain.Account account0 = new org.springframework.samples.jpetstore.domain.Account();
account0.setUsername("j2ee");
account0.setPassword(null);
account0.setEmail("yourname@yourdomain.com");
account0.setFirstName("ABC");
account0.setLastName("XYX");
account0.setStatus("OK");
account0.setAddress1("901 San Antonio Road");
account0.setAddress2("MS UCUP02-206");
account0.setCity("Palo Alto");
account0.setState("CA");
account0.setZip("94303");
account0.setCountry("USA");
account0.setPhone("555-555-5555");
account0.setFavouriteCategoryId("DOGS");
account0.setLanguagePreference("english");
account0.setListOption(true);
account0.setBannerOption(true);
account0.setBannerName("");

return account0;
}

The tutorial also demonstrates some of the limitations of the tool as well.

Caveats

TestDataCaptureJ was really meant to handle data objects which are designed to hold data and follow JavaBean conventions, e.g.

  • objects are created using constructors
  • fields has setter methods using standard naming convention
    e.g. a field named ‘userAccountName’ would have a public setter method ‘setUserAccountName()’

Also to intercept the processing of the application in order to log the data, the test data required must be an object that is either passed to a method as a parameter, or returned from a method as a return value.

Therefore it can’t currently handle object where this isn’t the case, e.g.

  • object that are not created with constructors, e.g. if they are created using factory methods instead
  • fields without setter methods or setter methods that don’t follow the standard naming convention
  • static fields (just not implemented)
  • objects pass into methods as varargs

There is some configuration that

How it works

Basically this is just a glorified version of the common AspectJ tracing example, using weave time loading to intercept the data objects that we are interested in. Only instead of just logging the contents, there is a 2 stage process:

  1. use java reflection to store access the field data recursively and store it in some metadata classes
  2. log the data as java code to file

There is a explanation page in the documentation that goes into more detail.

Please have a look at the code (or better still, fork it and play around with it) or read the documentation if you think this might be useful to you.