Monday, September 10, 2012

The Case of the Twin onSurfaceChanged() and the Split Action Bar

There's a funny little timing problem I ran into recently I'd like to share regarding window size and split action bars.

In an app I've got a loading screen that displays as we're making network requests.  It's a cute SurfaceView with some animation to keep users amused.  I'd written most of the initialization of assets inside of onSurfaceChanged() because I wanted to pre-scale everything to the exact size of the surface.

But a problem arose - for some reason onSurfaceChanged() was being called twice (and only when in portrait, not in landscape).  This was troublesome because it's somewhat a substantial method due to all the initialization.  What was happening?

It turns out that the split action bar was the culprit.  Here's how things went down:

1. In onCreate(), add the SurfaceView to the view hierarchy.

2. SurfaceView.onSurfaceChanged() would be called.

3. onCreateOptionsMenu() would then be called.  Since I had splitActionBarWhenNarrow set for the Activity, this would create the split action bar.

4. Due to the split action bar, the window size would change.  SurfaceView.onSurfaceChanged() would be called a second time.

The solution was to move the addition of the Fragment with the SurfaceView somewhere after onCreateOptionsMenu().  I put it in onPostResume() (but make sure to call it only once on initialization).

There is one alternative solution, which is to call invalidateOptionsMenu() (or supportInvalidateOptionsMenu(), if you're using the support library) during onCreate().  This will force onCreateOptionsMenu() to be called early.  I dislike this answer because it screws up the normal lifecycle for menu creation and can therefore readily cause confusing issues down the line.


  1. You may want to check Adam Powell's comment which offers a better solution to your problem here :

    1. I'm not sure it's necessarily "better" but an alternative. With his solution you have to adjust all your fragments to account for the extra padding at the top/bottom of the screen, which (depending on what other fragments you're working with) could be a pain. Adding the fragment in onPostResume() is a simple solution that doesn't really change any of the rest of the Activity.

      That said, in my particular case that would probably work better (since I was trying to figure out how to deal specifically with showing then hiding the action bar in that Activity).