Mono for Android by example: The action bar

My boss has lend me a Galaxy Tab which I am (with a greatful heart I can tell you) punishing with some Mono for Android tryouts. Although I wasn’t a big fan of Android Tablets at first I’m starting to like it now I know what I can do to it. There’s a lot more pixels I can use, so it opens up a whole new universe of possibilities.

In this article I’m going to show you one of the latest discoveries I did on the tablet. A lot of developers already know this feature, called the action bar. It’s a user interface pattern widely applied in the Android ecosystem. On versions of Android prior to v3.0 you needed to build the action bar yourself using a custom linear layout and a bunch of code. Very ugly and hard to maintain. But version 3.0 and up however support the action bar out of the box with a rich set of features. It makes things a lot easier, that’s for sure.

What is the action bar

So what is the action bar? It is the top section of the app on the tablet (or the phone if you have a phone that supports Android icecream sandwich). The action bar can be used to give information about the context the user is at now. It also provides common actions used throughout the app.

There’s a lot of stuff you can do with the action bar. For example the following set of controls are supported on the action bar:

  • Action items
  • Tabs
  • Dropdown menu
  • Action views

There’s even more possibilities you can use here, but I will cover those in another blogpost. For now I’m just going to cover the basics that you will probably use the most in your app.

Note: Keep in mind that the features discussed in this article apply to both Icecream sandwich and Android 3.0, you cannot use these features in versions before that. Infact if you try, some code may fail completely and other code (specifically XML files) may simply be ignored by the OS.

Displaying the action bar

To display the action bar you need to activate it as a window feature. The following shows the code you need for activating the window feature.

protected override void OnCreate(Android.OS.Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);

    // Activate the action bar and display it in navigation mode.
    RequestWindowFeature(WindowFeatures.ActionBar);

    // Initialize the view for the activity
    SetContentView(Resource.Layout.MainActivity);

    // Initialize the action bar
    InitializeActionBar();
}

An important thing to keep in mind here is that you need to request window features before you load the content view. If you do it the other way around, the window feature settings will simply be ignored.

After you have activated the action bar feature for the current window, you can access the action bar through the ActionBar property. If you haven’t activated the window feature, this property will return null.

Adding action items to the action bar

Action items are best compared to menu items in both their functionality and the way they are implemented when you’re building an app. On the phone the menu is displayed when you press the menu button on your phone. This is because there’s not alot of room on the screen. On a tablet however, there’s a lot more room, so Android displays them in the action bar.

Because action items are implemented as menu items you can load them by overriding the OnCreateOptionsMenu method on your activity. In this activity you can construct a new menu or just load a resource containing the menu items you want displayed.

public override bool OnCreateOptionsMenu(IMenu menu)
{
    MenuInflater.Inflate(Resource.Menu.ActionItems,menu);
    return true;
}

As you can see in the sample above, the menu inflater component does all the heavy lifting for us. After you have invoked the menu inflater you will see action items in the screen at top right. The look of the menu items is specified in the ActionItems.xml resource file.

image

To make the resource file for the action items you need to have a Menu folder as a child of the Resources folder in your Visual Studio project. This file will tell Android the action items you want to display and how to display them.

<?xml version="1.0" encoding="utf-8"?>
<menu >
  <item android_id="@+id/menu_save" android_icon="@drawable/ic_menu_save"
        android_title="Save" android_showAsAction="ifRoom|withText"/>
</menu>

The menu resource is just like a regular menu resource file, except there’s a new option that you can specify per item, with the name showAsAction. This property is available since API level 12 (Which is Android 3.0) if you run Android 2.3 or lower, this option is simply ignored by the OS.

The showAsAction option tells the OS how to display the action item. If you specify “never” it will hide the item under the menu button that gets displayed at the right hand side of the action bar.

image

If however you specify ifRoom, it will get displayed when there’s room on the screen. The OS will automatically hide the item when there isn’t enough room to show the item.

image

Of course if you always want the item to be displayed on the screen, you can specify the “always” setting for the showAsAction attribute in the menu file.

The last option you can specify is whether the action item should be displayed with our without text. If you append the withText option to the showAsAction attribute, you will get the icon of the action item and the display text for the action item.

image

Handling the actions is done just like a regular menu item, by overriding the OnOptionsMenuItemSelected method.

public override bool OnOptionsItemSelected(IMenuItem item)
{
    //TODO: Handle the selection event here.
    return false;
}

In this method you can detect which item was selected and perform the action the user requested to be executed.

Working with tabs

The tabs option for the action bar is actually one of the more fun ones if you ask me, It displays the flexibility of the Android 3+ API at its best. The tabs feature of the action bar combines the possibility to create custom tab like you can on the tab control with the power of fragments. Don’t worry if you don’t know anything about fragments, I will get to that in a minute.

To work with tabs you need to configure a bit more on the action bar itself. For the tabs to work you tell the action bar to go into tabs navigation mode. After you’ve done that, you can create a new tab and add it to the action bar.

private void InitializeActionBar()
{
    // Set the action bar to Tab mode
    ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;

    // Create a new tab (CreateTabSpec on the TabControl)
    var homeTab = ActionBar.NewTab();

    homeTab.SetTabListener(
        new TabListener<CrumbsOverviewFragment>());

    homeTab.SetText("Crumbs");

    // Add the new tab to the action bar
    ActionBar.AddTab(homeTab);
}

The important bits for a tab are the name to display and the handler to attach. If you don’t specify either of these you will end up with a bit of a cryptic exception at runtime, which I’m not going to repeat here.

Implementing the tab handler is done by creating a new class that derives from Java.Lang.Object and implements the ActionBar.ITabListener interface. The following snippet shows a basic implementation of this.

/// <summary>
/// Listener that handles the selection of a tab in the user interface
/// </summary>
/// <typeparam name="T"></typeparam>
public class TabListener<T> : Java.Lang.Object, ActionBar.ITabListener
    where T: Fragment, new()
{
    private T _fragment;

    /// <summary>
    /// initializes a new instance of the tab listener
    /// </summary>
    public TabListener()
    {
        _fragment = new T();
    }

    /// <summary>
    /// Initializes a new instance of the tab listener
    /// </summary>
    /// <param name="fragment"></param>
    protected TabListener(T fragment)
    {
        _fragment = fragment;
    }

    /// <summary>
    /// Handles the reselection of the tab
    /// </summary>
    /// <param name="tab"></param>
    /// <param name="ft"></param>
    public void OnTabReselected(ActionBar.Tab tab, FragmentTransaction ft)
    {

    }

    /// <summary>
    /// Adds the fragment when the tab was selected
    /// </summary>
    /// <param name="tab"></param>
    /// <param name="ft"></param>
    public void OnTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
    {
        ft.Add(Resource.Id.MainFragmentContainer,_fragment, typeof(T).FullName);
    }

    /// <summary>
    /// Removes the fragment when the tab was deselected
    /// </summary>
    /// <param name="tab"></param>
    /// <param name="ft"></param>
    public void OnTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
    {
        ft.Remove( _fragment);
    }
}

Notice the FragmentTransactions in the handler. When the user selects a tab, you are required to load a fragment on the user interface using the fragment transaction provided to you.

Note: Please try not to commit the fragment transaction. If you do, the app will crash. Google decided that you initiate the action as part of the transaction, but the transaction itself has to be committed by the OS. I find this very annoying, because if I don’t want the transaction to commit I cannot reset it or do anything with it for that matter.

Fragments is a new API added to Android 3.0, which allows you to move fragments of the user interface into separate components. These components can be loaded through the fragment manager by starting a new fragment transaction. When you start a new fragment transaction you’re telling the OS that you are about to navigate to a different part of the application. But instead of throwing the current activity on the backstack and moving on to another activity, you’re actually throwing the current fragment inside the allocated container on the backstack and replacing it with a new fragment. By adding the fragments to the backstack, you’re allowing the user to navigate between different parts of the user interface, without leaving the activity.

The power of fragments lies in the fact that you can compose them. For example on a tablet, you can have two fragments of 480 pixels wide side-by-side. On the phone this wouldn’t fit, so you navigate between the two. The code to handle the user interface interaction however stays the same for both the tablet and the phone, because it is still placed on the activity.

Note: Okay, enough commercial talk. This sounds cool on paper, but it only works properly on Android 3.0 and higher. For lower versions you need a compatibility pack. Which at this point isn’t supported on Mono for Android. Maybe they will fix it, maybe not. I suggest you save this feature for later Glimlach

/// <summary>
/// Adds the fragment when the tab was selected
/// </summary>
/// <param name="tab"></param>
/// <param name="ft"></param>
public void OnTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
{
    ft.Add(Resource.Id.MainFragmentContainer,_fragment, typeof(T).FullName);
}

Let’s move back to the OnTabSelected method on the tab listener component. This method is called when the user clicks on the tab. Within this method the new fragment to display is added to the transaction. You can add a fragment to the transaction without ever specifying a root container to add the fragment to. This however may not be the smartest idea. There’s no way of knowing where it might end up. So instead the ID of a control is specified which serves as a container.

Fragments can be loaded directly in a layout by using the <fragment> element. The other way of loading a fragment is by creating a ViewGroup (LinearLayout, TableLayout,RelativeLayout, etc.) element in the XML and giving that element an ID. When you’re about to load a fragment, you can specify that ViewGroup as the container for the fragment. Why a ViewGroup? ViewGroup controls are the only controls that can contain children. Android will try to merge the layout with the ViewGroup you specified in the fragment transaction, so it will need something to add the children from the fragment to.

Implementing the fragment itself is done by creating a new C# class that derives from Fragment. Within the fragment class you just created you can override the OnCreateView method to load a view resource.

public class CreateCrumbFragment: Fragment
{
    public override Android.Views.View OnCreateView(Android.Views.LayoutInflater inflater,
        Android.Views.ViewGroup container, Android.OS.Bundle savedInstanceState)
    {
        return inflater.Inflate(Resource.Layout.CreateCrumbFragment, container, false);
    }
}

Adding dropdowns to the action bar

The third way in which you can customize the action bar is by adding a dropdown to the action bar. For this mode of operation to work you need to switch the action bar to List navigation mode. This means you cannot combine the dropdown list with a tab navigation mode.

The following snippet demonstrates how you can configure the action bar in dropdown list mode.

private void InitializeActionBar()
{
    ActionBar.NavigationMode = ActionBarNavigationMode.List;

    ActionBar.SetListNavigationCallbacks(
        new NavigationSpinnerAdapter(this),
        new NavigationListener());
}

Items for the dropdown list are provided by a spinner adaper which you need to implement yourself. It is very similar to implementing a spinner adapter for a regular spinner control.

public class NavigationSpinnerAdapter : BaseAdapter
{
    private List<Java.Lang.Object> _spinnerItems;
    private LayoutInflater _layoutInflater;

    public NavigationSpinnerAdapter(Context context)
    {
        _spinnerItems = new List<Java.Lang.Object>();

        // Create java strings for this sample.
        // This saves a bit on JNI handles.
        _spinnerItems.Add(new Java.Lang.String("Sample item 1"));
        _spinnerItems.Add(new Java.Lang.String("Sample item 2"));
        _spinnerItems.Add(new Java.Lang.String("Sample item 3"));

        // Retrieve the layout inflater from the provided context
        _layoutInflater = LayoutInflater.FromContext(context);
    }

    public override Object GetItem(int position)
    {
        return _spinnerItems[position];
    }

    public override long GetItemId(int position)
    {
        return position;
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        var view = convertView;

        // Try to reuse views as much as possible.
        // It is alot faster than inflating new views all the time
        // and it saves quite a bit on memory usage aswell.
        if (view == null)
        {
            // inflate a new layout for the view.
            view = _layoutInflater.Inflate(Resource.Layout.SpinnerItem, parent, false);
        }

        var textView = view.FindViewById<TextView>(Resource.Id.DisplayTextLabel);
        textView.Text = _spinnerItems[position].ToString();

        return view;
    }

    public override int Count
    {
        get { return _spinnerItems.Count; }
    }
}

The spinner adapter can be derived from BaseAdapter to get a quick and easy foundation for it. It saves on hugely on implementation details. The important bits in the spinner are the items you’re returning to the action bar and the view that belongs to each item. Keep in mind here that memory management is a big thing with list adapters in general. So don’t go wild on creating new views every time the OS asks for one. Instead reuse them as much as you can.

Now that you have a source for the navigation items, you will also need a way to handle the selection of the items. For this you can create a new ActionBar.IOnNavigationListener component.

public class NavigationListener: Java.Lang.Object, ActionBar.IOnNavigationListener
{
    public bool OnNavigationItemSelected(int itemPosition, long itemId)
    {
        //TODO: Handle list selection

        return false;
    }
}

This component will receive the selection the user made in the list, upon which you can for example load a different fragment on the user interface or start another activity.

After you’ve gone through the ceremony with Android, you end up with list items like the ones in the screenshot below. Pretty nifty stuff.

image

Adding an action view to the action bar

The final option to expand the action bar is by adding an action view to it. Action views are custom views that display arbitrary controls on the action bar. (Although you only 56 px verically to work with).  You can do very neat things like adding a search widget to the action bar.

image

Configuring an action view is done by specifying the android:actionViewClass attribute in the menu resource file that I showed you earlier in this article.

<menu >
  <item android_id="@+id/menu_search" android_title="Save" android_showAsAction="always"
        android_actionViewClass="android.widget.SearchView"/>
</menu>

When the application loads the menu resource, the action view will get activated automatically and become available when you ask for it in the OnCreateOptionsMenu method.

public override bool OnCreateOptionsMenu(IMenu menu)
{
    MenuInflater.Inflate(Resource.Menu.ActionItems, menu);

    var searchView = (SearchView)menu.FindItem(Resource.Id.menu_search).ActionView;
    searchView.SearchClick += OnSearchClicked;

    return true;
}

So now when the user searches for a string in the search widget, you can respond to this action and filter the list on the user interface using the search string entered by the user.

Final thoughts

Google nailed it with the action bar for the tablet. It is one of the nicest peaces available to you in the user interface. Especially since so many apps are using the user interface pattern in their apps.

The action bar is pretty versatile too, The views/widgets/tabs/action items is one thing. You can also style the whole thing like I did. I made the action bar translucent which goes pretty good with the rest of the UI. If I find time and energy, I will do a blogpost on that too.