Monday, August 8, 2011

The Strange Case of Dr. Action and Mr. Bar

Lately, I've been trying to finally update the behavior of the ObjectForms Demo application on the Honeycomb tablet and one of the missing pieces was support for the ActionBar, the new feature of Honeycomb. Well, not that so new feature. ActionBar was promoted by Google engineers (such as this one) even before Honeycomb was published.

As many other app developers, ObjectForms tried to be a good Android citizen and followed the Google provided advice, so there is a custom implementation of the ActionBar related functionality for a quite some time and I thought since I crafted my interface to look similar to actual Honeycomb API so I thought this will be piece of cake.

With some awkward reflection and dynamic invocation code (I wanted the same codebase to work on pre-Honeycomb as well) I was set to go. To my surprise, I the ActionBar was not showing up on the Motorola Xoom device no matter how hard I tried. The ActionBar was not there and this call :

First, documentation says that if the ActionBar is visible or not, is controlled by the changes of (!!!!) visual theme ! WOW, that's example of very bad engineering. Sure I did try to apply the correct theme, but it made no difference.

Second thing that I found very unusual was the fact that ActionBar, although very visual component itself, able to contain other visual components (tabs, custom components etc.) is not part of the visual hierarchy and is not descendant of View class, but rather just plain Object. It made no sense to me.

I was ready to find some workaround. Let's keep my original, custom ActionBar, and just put the button that would show the menu - once you declare in the Android manifest that you target Honeycomb device the "compatibility" menu (located in the bar on the bottom of the screen, next to the "Back" and "Home" buttons) is gone and you can only access it from the ActionBar (in the top right corner). But to my great surprise, the method :

was doing nothing, although it worked perfectly well on the Gingerbread and earlier Android devices. Hm.

Typically, I would explore the Android source code to see what might be the reason for that unusual behavior, but Honeycomb is not quite open source ;-( so I was out of luck there. Seems I hit the wall.

The next effort was to understand what the hell this visual theme actually does to hide the ActionBar. Problem was, that content of the theme is not public either. But trying to search for any clues, I got to this page that helped me to solve the entire mystery. Yeah, title might be that important attribute. As I had my own implementation of ActionBar on the top, I wanted to maximize the available space and asked the window manager to hide a title for me. With single line of code commented out :

And the magic is gone and Honeycomb ActionBar is there. It is really just a glorified title bar, so it must be visible in Honeycomb to behave correctly. This little fact is mentioned in the Android Honeycomb documentation, so this is my first take-away for people trying to cope with the same problem :

Make sure that title of window is visible if you want to show the Honeycomb ActionBar !

Since I believe most developers face the similar scenario I had, they have their own implementation of the ActionBar for Gingerbread and as the result they had a window without the title, the solution Google has for the Honeycomb is extremely unintuitive. Glad I found a solution.

Not only this, but to my surprise I found that openOptionsMenu() started to work as well. That's amazing !

But I was still not done. As you can see from the picture above, the first version had both native Honeycomb ActionBar and the custom Gingerbread one.
I wanted to reorganize my code to be more reusable and in middle of this I realized the ActionBar is gone again, the getActionBar() is returning nothing again. But I was able to track my changes, so it didn't take long to figure out what is problem this time. I tried to create my visual hierarchy first and expected that getActionBar() will tell me if I have native HoneyComb ActionBar or not. But this method is useless, as it assumes setContentView(view) has already been called. Since I was just constructing that view, this was not the case. Of course, this is another quite important aspect never mentioned in the documentation.

Hey Google, if your code requires the contract, and you are not able to enforce it by API design, please do us a favor and let us know about the contract !

So this is my second take-away as the case gets some clarification :

Make sure the setContentView(view) has been already called for your Activity, otherwise getActionBar() will return null.

Just to make the story complete, I wanted to have the look of the native ActionBar to be similar to previous version, and wanted to replace the logo with one that doesn't look as been a part of the button. I was not shocked to learn that customization is possible only in form of replacing the XML attribute "logo" with a custom value, and there is no corresponding Java API for that. It is still not clear to me why ActionBar is not descendant of View and why it is so difficult to deal with it, but apart of that, all the mystery has been solved.

As you can see, I decided to solve this customization problem by hiding the native ActionBar and rely on the fact that menu button in the top right corner works as expected. In this context, perhaps you'd think that I could save a lot of hassle and skip the ActionBar completely. There is one important aspect, which is the third and final take-away from this post :

If you target Honeycomb in the manifest, you need native ActionBar to be at least hidden, otherwise there is no way to show the option menus.

Hope you find the information mentioned in this post useful and hopefully they can save you some head-scratching time I had recently.