Titanium Apps With Android Action Bar, TabGroups and Menu Items

In Titanium Studio, the template Two-tabbed Alloy Application project makes for a natural starting point.

But if you launch it on an Android emulator running 3.0 (API level 11) or above, the executed app looks just the same as if you were running it on one of the older Android APIs.

Alloy 2 Tabbed Default Project Android

The left side screen shot shows the app running on Android 2.3.3 (API 10) while the one on the right is running on 4.2.2 (API 17). I have no idea why the emulator uses this monochrome style (and couldn’t find any when I looked), I assume it’s to highlight what – if any – changes you have made to the default UI colours by only showing those in the true colour?

The following sections of this article will continue with the imported Titanium Two-tabbed Alloy Application and update it to take advantage of the native UI features present in the APIs from level 11 up (Action Bar with incorporated TabGroup and Menu Items).

In these API versions that support the Action Bar, Android now expects you to develop applications that deliver a user experience and design consistent with their recommended patterns.

Configuring a Titanium Created Android Virtual Device (AVD)

When Titanium creates an Android emulator for you after selecting Run or creating a Run Configuration, you can see the title in the top left corner of the emulator window when running. If you open the AVD Manager, you will be able to find and configure these. But you need Titanium to create it first.

In the above, you can see they have been automatically named titanium_5_WVGA854_x86 and titanium_17_WVGA854_x86.

The emulator name is important to know, because the Android devices from 3 (API 11) upwards switched from physical hardware buttons to touchscreen buttons (and the menu button was replaced with a “recent apps” button meaning apps for these devices need a menu button in the app UI).

So for these newer models, once your AVD has been created by Titanium the first time it is run, you need to go into the device manager and manually make a hardware change to get the Home, Back and Recent Apps buttons to appear at the bottom of the screen.

Android AVD Set No Hardware Back and Home Buttons

After doing: AVD > titanium_17_WVGA854_x86 > Edit > Hardware > New > Hardware Back/Home keys = No, I end up with an emulator configured like the above screen shot. So reloading the Two-tabbed Alloy Application I get the following emulator layout:

Alloy 2 Tabbed App With No Hardware Buttons

The reason the 4th virtual button is showing (the 3 vertical dots) is because by default Titanium builds to 2.2 (API 8). As such, Android automatically adds a menu button to the controls. If a more recent SDK target was used for the build, the menu button would appear on the Action Bar instead.

Android Action Bar

It doesn’t take much to get the Action Bar and modern Holo style to render in the app when ran in the 4.2.2 emulator configured above (or any of the other 3.X, 4.X versions ).

Just make the changes shown below to the tiapp.xml XML file (located in the top level of the project folder). By default, the tiapp.xml file opens in a custom edit mode with text fields, but at the bottom of the editor window are the tabs to switch between the form and xml editing modes.

Replace:

<android xmlns:android="http://schemas.android.com/apk/res/android"/>

With:

<property name="ti.android.fastdev" type="bool">false</property>
<android xmlns:android="http://schemas.android.com/apk/res/android">
	<tool-api-level>14</tool-api-level>
	<manifest>
		<supports-screens android:anyDensity="false"/>
		<uses-sdk android:maxSdkVersion="17"
			android:minSdkVersion="8" android:targetSdkVersion="14"/>
	</manifest>
</android>

The first element which sets ti.android.fastdev to false is related to a bug caused when using fastdev and targetSdkVersion on Android emulator versions greater than 4.0.3 which I have explained here in more detail.

Inside the manifest element itself, is another element that I used to fix/hack Tab Group icon sizes: supports-screens android:anyDensity=”false”. You can see this when comparing the icon size in the screen shots above with those below.

I know the correct way would be to follow the official Android guidelines for icons, but this property enables me to quickly toggle between larger and smaller icons. Add it for larger icons, remove it for smaller.

The other elements/properties are what setup the SDKs and tell Android what versions we are expecting to support. minSdkVersion and maxSdkVersion should prevent the app installing on devices outside of this range, although I’m not sure how gracefully this is handled by these devices!

Alloy 2 Tabbed Action Bar – Portrait Mode

Alloy 2 Tabbed Action Bar Holo Dark Portrait

Alloy 2 Tabbed Action Bar – Landscape Mode

Alloy 2 Tabbed Action Bar Holo Dark Landscape

Alloy 2 Tabbed Action Bar – Holo Light – Landscape Mode

Alloy 2 Tabbed Action Bar Holo Light Landscape

These screen shots are with the above changes made to the tiapp.xml. To toggle the emulator between portrait and landscape modes: Ctrl-F11.

The Holo Light theme is achieved by adding the following xml field inside the manifest tag

<application android:theme="@android:style/Theme.Holo.Light"/>

Home Icon Clicks, Back Button and Title

At the moment, the top left corner of the app is only showing the app icon. Clicking on it has no effect either.

We can handle clicks on the icon, set a title, and add the back icon to the logo.

Android Action Bar With Back Button and Title

In the above screen shot you can now see a title and the back button next to the home logo. Clicking it pops up an alert. To achieve this:

1. controllers/index.php – replace $index.open() with the below:

$.tabGroup.addEventListener('open', function(e) {
    var activity = $.tabGroup.activity;

    if( Alloy.Globals.Android.Api >= 11 ) {
	    activity.actionBar.title = "DemoApp";
		activity.actionBar.displayHomeAsUp = true; 
	    activity.actionBar.onHomeIconItemSelected = function() {
	        alert("Home icon clicked!");
	    };  
    }
});

$.tabGroup.open();

2. views/index.xml – change the opening TabGroup tag to:

<TabGroup id="tabGroup">

3. alloy.js – add:

if( OS_ANDROID ) {
	Alloy.Globals.Android = { 
		"Api" : Ti.Platform.Android.API_LEVEL
	};
}

Giving an id to the TabGroup allows us to reference it directly, but means the controller should call .open() against it rather than $.index as it was doing.

Because the Action Bar is only available in Android from API 11 onwards, we need to check the API before trying to work with it. The Ti.Platform.Android.API_LEVEL property lets us get that.

But, these calls are expensive performance wise, so rather than littering the code with them, I get it once and push it to the Alloy.Global so it is accessible throughout the app.

Then in the controller we test against Alloy.Globals.Android.Api before setting the title, setting the displayHomeAsUp to get the back caret, and handling clicks on the icon itself.

Adding Menu Items

Lastly we need to add some menu items. How these are rendered will depend on the space available. If there is not enough space, or you select to hide them (either permanently or when there is not enough space), the overflow menu icon will appear in the top right corner (the 3 vertically stacked dots).

Action Bar With Logo, Title, Tabs and Menu Items

In this screen shot 3 menu items were created: 2 that were set to be displayed at all times, and 1 set to never been shown – hence the appearance of the overflow menu icon.

Action Bar With Logo, Title, Tabs and Menu Items Hidden Drop Down

This screen shot above shows the theme changed to Holo Dark (achieved by editing the manifest entry in tiapp.xml, changing Theme.Holo.Light to Theme.Holo – and not adding Dark as you might assume). Also, all menu items have been set to show never, but are visible here in the drop down having clicked on the overflow icon.

In the Appcelerator Android Action Bar documentation section you can see more information about the following display options for menu items which are pretty self explanatory:

  • SHOW_AS_ACTION_ALWAYS
  • SHOW_AS_ACTION_IF_ROOM
  • SHOW_AS_ACTION_NEVER
  • SHOW_AS_ACTION_WITH_TEXT
  • SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW

Adding Menu Items In The View XML

<Alloy>
	<TabGroup id="tabGroup">
		<Tab title="Tab 1" icon="KS_nav_ui.png">
			<Window title="Tab 1">
				<Label>I am Window 1</Label>
				<Menu id="menu" platform="android">
					<MenuItem id="menuItem" title="Item 1" icon="images/action_about.png" showAsAction="Ti.Android.SHOW_AS_ACTION_ALWAYS" />
					<MenuItem id="menuItem" title="Item 2" icon="images/action_settings.png" showAsAction="Ti.Android.SHOW_AS_ACTION_ALWAYS" />
					<MenuItem id="menuItem" title="Item 3" icon="images/action_about.png" showAsAction="Ti.Android.SHOW_AS_ACTION_NEVER" />
				</Menu>				
			</Window>
		</Tab>
		<Tab title="Tab 2" icon="KS_nav_views.png">
			<Window title="Tab 2">
				<Label>I am Window 2</Label>
			</Window>
		</Tab>
	</TabGroup>
</Alloy>

The above code was used to generate the menus in the above examples. It is the standard index.xml view from the default alloy 2 tabbed application, but with the Menu elements slotted in (and the id added earlier to the TabGroup).

I don’t like – and shouldn’t – be adding this menu to a specific Window and not the TabGroup on the top level, but this was the only way I could get it to work using XML in the view. Adding it anywhere else gave me weird results.

I would expect shortly that you will be able to mark up menus in the XML either for all windows within the TabGroup, or a different menu for each Tab.

Adding Menu Items In The Controller

Instead of having Alloy generate our menus by way of XML, we can do it ourselves in the controller. This approach is more versatile and can probably be used to set Tab window specific menus too (I’ve not delved into this just yet).

$.tabGroup.addEventListener('open', function(e) {
    var activity = $.tabGroup.activity;

    if( Alloy.Globals.Android.Api >= 11 ) {
	    activity.actionBar.title = "DemoApp";
		//activity.actionBar.displayHomeAsUp = true; 
	    activity.actionBar.onHomeIconItemSelected = function() {
	        alert("Home icon clicked!");
	    };  
    }

	// Menu Item Specific Code
    activity.onCreateOptionsMenu = function(e) {
        var menu = e.menu;
        
        // Menu Item 1
        var menuItem1 = menu.add({
            title : "Item 1",
            icon  : "images/action_about.png",
            showAsAction : Ti.Android.SHOW_AS_ACTION_ALWAYS
        });
        menuItem1.addEventListener("click", function(e) {
            alert("I was clicked 1");
        });   
        
        // Menu Item 2
        var menuItem2 = menu.add({
            title : "Item 2",
            icon  : "images/action_settings.png",
            showAsAction : Ti.Android.SHOW_AS_ACTION_ALWAYS
        });
        menuItem2.addEventListener("click", function(e) {
            alert("I was clicked 2");
        });
    };  
});

$.tabGroup.open();

When looking for information related to ActionBar, TabGroups and Menus, this Bug report and this Support thread came in handy.

Other Info

Running the code on Android versions older than 11, will result in a 2 tabbed application that looks much like what we started off, except the menu items will pop up at the bottom of the screen when the menu button is pressed.

When adding things to the ActionBar, if too many items are added (tabs, menu items) unexpected things can happen. The title (if set) disappears, the tab icons or text might disappear. Also, any menu items set to SHOW_AS_ACTION_ALWAYS will just disappear and not even appear in an overflow menu.

Menu items when rendered in the overflow menu are text only – no icons.

When working with iOS, you may prefer to segregate the controllers and actions into platform specific files instead of if and else statements in the same files (or in the case of view files, iOS specific markup alongside Android markup).

Creating controllers/android/index.js but keeping controllers/index.js, ensures the Android index controller will be used on Android, and the other index controller for the other platforms (iOS and Mobile web).

When fiddling with this I did have to also add an Android specific view file view/android/index.js, so it would seem at the moment if you to create a platform specific controller you will need to also create a platform specific view.

GitHub Code

Some code files used in the above examples can be found at: https://github.com/websterj/Alloy2TabbedAndroidUI

It’s not the entire Alloy project contents, but a stripped down version that includes the tiapp.xml, the relevant MVC files and some image assets.

5 Responses to Titanium Apps With Android Action Bar, TabGroups and Menu Items

  1. Felipe Campos Clarke July 23, 2013 at 15:32 #

    very good article. Thanks for sharing your knowledge

  2. Nirmal Natarajan November 21, 2013 at 01:38 #

    I’ve been searching for a sample code, and this helped me understand what it takes to have that sleek bar. Thanks for sharing!

  3. darkneo December 6, 2013 at 07:39 #

    Thank you so much !!!

  4. Michael Stelly December 14, 2013 at 16:09 #

    Not sure how you got this to work. In my app, this line crashes the emulator

    and 14 needs to be a child element of or again, the emulator crashes.

  5. Onoma July 14, 2014 at 08:30 #

    Thanks for your guide!

    I have one question. In your example you’re using both an icon and a title on your menu items. Example:
    var menuItem1 = menu.add({
    title : “Item 1″,
    icon : “images/action_about.png”,
    showAsAction : Ti.Android.SHOW_AS_ACTION_ALWAYS
    });

    but I cannot see inside a menu item both an icon and a title, only the title shows up.

    Is it possible to display both an image and a title?

Leave a Reply