help

Lecture 9 - Fragments and ActonBars

The Android framework contains a number of UI components that allow a common feel to design simple UIs -- one is the ActionBar. These UI components integrate fragments which are associated with a view. Fragments let us build reusable and extensible UIs.

What this lecture will teach you

Resources

Checkout the demo project

ActionTab app

ActionTab is a simple application used to demonstrate the use on the ActionBar and fragments. If you click through the tabs you will find to answer to the question - why Dartmouth.

Fragments are associated with the UI. We will develop the ActionTab app to best illustrate how fragments can use Android UI components such as ActionBar (more on that in a moment). The ActionTab app will combine multiple fragments (four in all) in a single activity to build a multi-pane UI. These fragments can be considered self-contained components and could be reused by multiple activities potentially across multiple applications.

The app is made up of 4 tabs and 4 corresponding fragments.

Note, that when the app is shown in landscape the ActionBar is configured differently from the normal portrait view -- Android rearranges the icons visible in the actionbar; that is, in landscape the "add" option is shown as an icon on the actionbar rather than in the drop down, as is the case in portrait mode; we show the landscape below:

Play with the app to get a feel for the UI -- cool hey? Simple, compact and a nice way to build up the features and services offered by your app. We will use this ActionBar and fragment combination as part of Lab2.

There is one activity and four fragments in this project:

Fragments

Up until now we have used activities as drivers to display views to the screen. The previous example each screen is associated with an activity that is used to render and manage the user interaction with the view. All that changes in this lecture. We move away from activities and start to use fragments -- a fragment is now used to render and manager a particular UI view. Think of fragments as lightweight activities for now. Your application still needs activities but we don't use them to solely render views. Most applications, and ones we will develop, include one or more activity and fragments.

What are fragments?

In implementation there are considerable differences; for example, an activity has to be specified in the manifest, while fragments do not because they can only exist in the context of an activity and are coupled to the activity state. While fragments are strongly integrated into the UI you can use fragments without a view or UI components. Fragments can implement a number of application behaviors, such as, code to handle listeners, files, database, etc. These types of fragments can be thought of as invisible worker for the activity -- more later on this. For now we will just consider fragments when applied to the UI.

Why use fragments over activities?

A fragment is a modular part of an activity. It has its own lifecycle and receives its own input events. Fragments can created, added or removed while the activity is running. Consider a fragment a "sub activity" that you can reuse in different activities. But you could ignore fragments and just use activities if you wish -- but not recommend. Why use fragments over activities? Well fragments are wired to create reusable Android user inferences components -- however, it is true to say you can implement similar application UIs with a pure activity solution, which might be confusing for students. Here are some reasons why you should use fragments as a designer:

Fragment and activities akin to processes and threads

Fragments are reusable UI but can also be considered lightweight in how they handle operations on the UI. As I mentioned before you can do most things with activities that fragments do but consider for the moment (if you did this) the relationship between design using all processes or threaded design. Fragments can be considered to be threads in such a choice, as shown below.

Fragment Lifecycle

Though Fragment defines its own lifecycle, that lifecycle is dependent on its activity: if the activity is stopped, no fragments inside of it can be started; when the activity is destroyed, all fragments will be destroyed.

A fragment runs with in the activity and the activity lifecycle that we discussed. So it an activity is stopped so are all its fragments. Similarly if the activity is destroyed so are all the associated fragments. However, when an activity is running (i.e., onResume() state) fragments can be independently managed -- added, replaced or removed. As we will see what we discuss the ActionTab application we will use fragment transactions to remove and replace fragments associated with the action bar tabs: such as:

These fragment transactions allow the activity to manage the back stack - a back stack entry in the activity is a record of the fragment transaction that occurred. This, for example, allows the user to reverse a fragment transaction (navigate backwards), by pressing the back button.

The fragment lifecycle diagram shown below looks complicated but as we discuss it consider that this set of fragment states run inside of the running state of the activity lifecycle diagram we discussed in earlier lecture on activity lifecycle. Therefore the state diagram shown below is only valid when the activity associated with the fragment

Like the activity lifecycle the fragment lifecycle is represented by a bunch of states and associated event handlers, which are triggered when a fragment is:

In addition there are a number of callbacks associated with a number of fragment specific events, such as, 1) binding and unbinding a fragment from its activity; 2) when a fragment is created and destroyed. If you look at the core states shown in the figure you see:

Fragment lifecycle events

Many events can occur between these states -- for example the user can hit the back button.

If we consider the ActionTab app it has four fragments - one for each tab view. As the use taps through the tab options the fragment transitions through the states in the lifecycle diagram -- moving say from being in focus in onResume() to hidden in onPause(). But the complete fragment lifecycle is tightly coupled to the activity but also governed by the user interaction with the UI (as is the case with the ActionTab app). The activity can dynamically construct the UI and its associated fragments -- such as adding and removing fragments where each fragment transitions through its full, visible and active lifecycle several times within the lifetime of its parent activity -- for example as a fragment is added and removed from the view.

See MySkeletonFragment.java for an example of a skeleton fragment. There are many events and we will only use a few of these events in terms of populating code. So don't be overwhelmed -- at least, not yet ;-). Let's discuss some of the important events.

onAttach() and onDetach() events

The lifecycle starts and ends when a fragment is attached and detached from its parent activity. The onAttach() event occurs before the fragment's UI is created and before the fragment or its parent activity have completed initialization. Keep this in mind when we are looking at the ActionTab activity's onCreate() when it creates its fragments. It is a good idea to create fragments in the activities onCreate() to ensure they are created only once in the fragment lifetime.

onCreateView() event

The fragment's UI is created and destroyed in onCreateView() and onDestroyView() callbacks, respectively. The onCreateView() method initializes the fragment by inflating the UI, getting references and binding any data to the views that it contains -- and then any addition initialization such as setting up services (we will talk about services later on in the course).

onCreateView(): ActionTab example

The onCreateView()'s main job in code we will write is a one liner -- taken from FindFragment.java in the ActionTab app:

return inflater.inflate(R.layout.findfragment, container, false)

An example of a fragment layout

The R.layout.findfragment is the ID for an XML layout resource to load (e.g., R.layout.findfragment). This is defined in the res/layout/findfragment.xml of the ActionTab project. This specifies the UI for the fragment, which is just a simple image but could be a sophisticated view group.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <ImageView
        android:id="@+id/imageViewOfGirl"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:contentDescription="@string/ui_love_picture"
        android:orientation="horizontal"
        android:src="@drawable/ic_eye" />
 
</LinearLayout>

Placing fragment layouts into a container view at runtime

The container parameter used in the inflate method is the optional view to be the parent of the generated hierarchy in this case the XML in res/layout/main.xml of the ActionTab project. This is shown below and represents a simple hierarchical LinearLayout where the fragment container is defined. ActionTab dynamically modifies layouts by adding, removing and replacing fragments at runtime. It does this by creating layouts such as findfragment.xml (and the other three layouts in ActionTab) at runtime and then load or placing them into a container View such as main.xml below. The app does this based on the user interaction -- and the system.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_gravity="center">
            <LinearLayout
                android:id="@+id/fragment_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent" >
 
 </LinearLayout>
</LinearLayout>
 

We will come back to these xml files when we discuss ActionTab in more detail. main.xml

ActionBar

ActionBar is a simple UI component that can be used by many different applications -- it gives a common feel that can be customized to the specific needs on different applications. Let's take a closer look at the action bar of our app -- now shown in portrait mode, as shown below. The action bar shows the application icon on the left -- the CS logo, followed by the activity title -- ActionTab. Our activity defines a number of "action items" (in menu/main.xml). Selected items can be made visible on the action bar such as search and send (which are directly accessible as icons defined in the menu/main.xml). If a menu item does not appear as an action item (for example add, share, feedback) then the action bar places it in the overflow menu -- the action item icons and the overflow menu are down in the figure(left). The overflow menu button is shown on far right of the figure -- 3 dots stacked. If we click on that we see the hidden menu options -- shown in the figure (right).

fragments

fragments

From your activity, you can retrieve an instance of ActionBar by calling getActionBar().

Systems design

The systems design diagram (below) captures the main components and their interaction. This is not meant to be a detailed diagram. Rather, it shows the main activity, the four fragments and their layouts. The menu and manifest are also shown.

Setting up the menu with action Items

The menu declares all of the menu items. We show a few items below. The object ID is set up; note Android has a number of nice icons we can use and in the case below we use the search and send icons from @android:drawable; regards "ifRoom": You can specify that a menu item appear as an action item in the XML file by using android:showAsAction="ifRoom" as shown above in our menu for each of the element. Depending on space (could be a tablet or phone, or could be a phone flipping between portrait and landscape) the item might be in the actionbar or if there is no room it will appear in the overflow menu. The final of the item set up is a title @string/ui_menu_send that can be used in the overflow menu if it can't be accommodated in the action bar.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
 
    <item
        android:id="@+id/menuitem_search"
        android:icon="@android:drawable/ic_menu_search"
        android:showAsAction="ifRoom"
        android:title="@string/ui_menu_search">
    </item>
    <item
        android:id="@+id/menuitem_send"
        android:icon="@android:drawable/ic_menu_send"
        android:showAsAction="ifRoom"
        android:title="@string/ui_menu_send">
    </item>
 
    **snippet**
</menu>

Creating the ActionBar and Tabs

We first get a reference to the actionBar and then set the navigation mode:

There are two other navigation modes:

We create (and return) 4 ActionBar.Tab. These tabs will not be included in the action bar until they are added. The name of the tabs are stored in strings.xml and references here.

Our design uses tabs to switch between Fragment objects, as discussed below.

public class MainActivity extends Activity {
 
            /** Called when the activity is first created. */
            @Override
            public void onCreate(Bundle savedInstanceState) {
                        super.onCreate(savedInstanceState);
                        setContentView(R.layout.main);
 
                        // ActionBar
                        ActionBar actionbar = getActionBar();
                        actionbar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
 
                        // create new tabs and set up the titles of the tabs
                        ActionBar.Tab mFindTab = actionbar.newTab().setText(
                                                getString(R.string.ui_tabname_find));
                        ActionBar.Tab mChatTab = actionbar.newTab().setText(
                                                getString(R.string.ui_tabname_chat));
                        ActionBar.Tab mMeetTab = actionbar.newTab().setText(
                                                getString(R.string.ui_tabname_meet));
                        ActionBar.Tab mPartyTab = actionbar.newTab().setText(
                                                getString(R.string.ui_tabname_party));
 

Once the tabs are set up we create 4 new fragments; we simply use the new key word in front of the class names for each of the fragments created in the files:

These are all pretty much identical code. Each fragment inflates its own layout (e.g., R.layout.chatfragment) in onCreateView().

Next, we set up the TabListeners on each of the created tabs for each of the fragments. Note, the constructor passes the reference to the fragment and the application context, which is used by the callbacks to display toast. Once we have created new TabListener interfaces and bind the fragments we then add the tab to the ActionBar for each of the tabs.

                        // create the fragments
                        Fragment mFindFragment = new FindFragment();
                        Fragment mChatFragment = new ChatFragment();
                        Fragment mMeetFragment = new MeetFragment();
                        Fragment mPartyFragment = new PartyFragment();
 
                        // bind the fragments to the tabs - set up tabListeners for each tab
                        mFindTab.setTabListener(new MyTabsListener(mFindFragment,
                                                getApplicationContext()));
                        mChatTab.setTabListener(new MyTabsListener(mChatFragment,
                                                getApplicationContext()));
                        mMeetTab.setTabListener(new MyTabsListener(mMeetFragment,
                                                getApplicationContext()));
                        mPartyTab.setTabListener(new MyTabsListener(mPartyFragment,
                                                getApplicationContext()));
 
                        // add the tabs to the action bar
                        actionbar.addTab(mFindTab);
                        actionbar.addTab(mChatTab);
                        actionbar.addTab(mMeetTab);
                        actionbar.addTab(mPartyTab);

Creating fragments and inflating: MeetFragment.java example

A fragment extends the Fragment class as shown below. When the MainActivity creates the fragments it calls onCreateView() for all fragments. All onCreateView() creates and returns the view hierarchy associated with the fragment-- essentially it returns the view for the fragment's UI. The code below overrides the onCreateView() handler into inflate and return the requires view hierarchy; that is, R.layout.meetfragment, as shown below. Note, if your fragment does not use a UI (such as providing the activity with background behavior) you do not inflate of course.

Importantly, fragments do not have to be registered in the manifest like activities are. And, as the fragment lifecycle, fragments are tied to the state of the activity so this makes good sense; that is, they can't exist without an activity.

public class MeetFragment extends Fragment {
            
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.meetfragment, container, false);
    }
    
}

Inflating the menu

The onCreateOptionsMenu() callback is invoked when the activity is created to inflate the main.xml in the menu folder as discussed above -- this inflates the XML menu resources that populates the he action bar and overflow menu. You can add menu items to the menu programmatically using onCreateOptionsMenu() but in our example we simply inflate the items set up in main.xml.

            @Override
            public boolean onCreateOptionsMenu(Menu menu) {
                        MenuInflater inflater = getMenuInflater();
                        inflater.inflate(R.menu.main, menu);
                        return true;
            }

Selecting menu items

If you added the menu item from a fragment, via the Fragment class's onCreateOptionsMenu callback, then the system calls the respective onOptionsItemSelected() method for that fragment when the user selects one of the fragment's items. When any of the options are selected by the user the onOptionsItemSelected is called with the reference to the related MenuItem. In most cases the callback justs displays toast but in the case of quit we call finish() to close the app.

            @Override
            public boolean onOptionsItemSelected(MenuItem item) {
                        switch (item.getItemId()) {
                        case R.id.menuitem_search:
                                    Toast.makeText(this, getString(R.string.ui_menu_search),
                                                            Toast.LENGTH_LONG).show();
                                    return true;
                        case R.id.menuitem_send:
                                    Toast.makeText(this, getString(R.string.ui_menu_send),
                                                            Toast.LENGTH_LONG).show();
 
                        ** snippet**
 
                        case R.id.menuitem_quit:
                                    Toast.makeText(this, getString(R.string.ui_menu_quit),
                                                            Toast.LENGTH_SHORT).show();
                                    finish(); // close the activity
                                    return true;
                        }
                        return false;
            }
 

Implementing the ActionBar.TabListener interface:

The activity implement the TabListener interface, since we are passing it into the setTabListener() method. There are three methods you must implement on that interface:

When the user touches a tab the following MyTabsListener will be invoked -- different callbacks are invoked depending on the user action. Toast is included so you can see the action on the display as you use the application. For example, when the ActionTab starts the constructor sets up the fragment and context for each of the 4 tabs -- 4 objects are created by the MainActivity, as discussed earlier. Then onTabSelected is called for the default tab, which is the one in focus -- in this case, the find tab.

If the user selects find again then the: onTabReselected() callback is invoked. There is nothing in this callback other than some toast. If the user then selects chat then the following actions take place:

Tab switching is managed by using the TabListener through the creation of fragment transactions in response to the user tapping the tabs; that is the user selects, unselects, and reselects tabs. These fragment transactions are managed inside of the TabListenser interface, shown below. If you look at the interface it comprises the constructor and the tab selected, unselected and reselected. A new fragment is only added in the onTabSelected() where the ft.replace(R.id.fragment_container, fragment) is invoked to switch fragments and views.

class MyTabsListener implements ActionBar.TabListener {
            public Fragment fragment;
            public Context context;
 
            public MyTabsListener(Fragment fragment, Context context) {
                        this.fragment = fragment;
                        this.context = context;
 
            }
 
            @Override
            public void onTabReselected(Tab tab, FragmentTransaction ft) {
                        Toast.makeText(context, "Reselected!", Toast.LENGTH_SHORT).show();
 
            }
 
            @Override
            public void onTabSelected(Tab tab, FragmentTransaction ft) {
                        Toast.makeText(context, "Selected!", Toast.LENGTH_SHORT).show();
                        ft.replace(R.id.fragment_container, fragment);
            }
 
            @Override
            public void onTabUnselected(Tab tab, FragmentTransaction ft) {
                        Toast.makeText(context, "Unselected!", Toast.LENGTH_SHORT).show();
                        ft.remove(fragment);
            }
            
}

Saving temporary app state

The method onSaveInstanceState(Bundle) is called before placing the activity into background, allowing you to save dynamic instance state in your activity into the given Bundle. This can be used later in onCreate(Bundle) if the activity needs to be re-created. onSaveInstanceState() is used to "remember" the current state when a configuration change occurs such screen orientation change. This is not meant for "long term persistence". We store the tab navigation here and restore it in onCreate().

            @Override
            protected void onSaveInstanceState(Bundle outState) {
                        super.onSaveInstanceState(outState);
                        Toast.makeText(
                                                this,
                                                "onSaveInstanceState: tab is"
                                                                        + getActionBar().getSelectedNavigationIndex(),
                                                Toast.LENGTH_SHORT).show();
                        outState.putInt(TAB_KEY_INDEX, getActionBar()
                                                .getSelectedNavigationIndex());
 
            }

What is Context?

In code examples you will see Context being passed around frequent. But what is context? Context is the current state of the application, activity, and object. It allows newly created objects to understand what has been going on. An application or its related activities have access to underlying system resources. You need that context to get at those services. Context is an entity that represents various environment data: local files, databases, class loaders associated to the environment, services including system-level services, and more. The application and activities inherit the Context object. Typically, you will need to get an reference to the Context to get information regarding the current object (this) or another part of your program (activity, package/application).

You can get the context a number of different ways by invoking

As a fairly simple run use this or getApplicationContext() to get a reference to Context object. This is a dumb and simple rule but may not always work/

Here are a couple of examples:

            public boolean onOptionsItemSelected(MenuItem item) {
                        switch (item.getItemId()) {
                        case R.id.menuitem_search:
                                    Toast.makeText(this, getString(R.string.ui_menu_search),
                                                            Toast.LENGTH_LONG).show();
                                    return true;

Because onOptionsItemSelected() is in the context of the activity it uses this in the toast above. This this is the activity context that has access to the resources such as R.string.ui_menu_search for example.

But what happens in the case where the code does not have access to the context? Here is the next example. Consider the TabListener interface. Here you cannot use this because it does not refer to the MainActivity (which is what you want to display the toast) but the handler. Therefore, because you need the Context of the MainActivity you need to have that set up before as we do in the code.

Because the MyTabsListener is outside the scope of the MainActivity and listener needs to get the context to display toast we pass the application context getApplicationContext() in onCreate() of the MainActivity to the MyTabsListener constructor and save it in context -- for use later in the toast.

First we pass the getApplicationContext() to the MyTabsListener constructor in the MainActivity's onCreate() as shown below.

                        // bind the fragments to the tabs - set up tabListeners for each tab
                        mFindTab.setTabListener(new MyTabsListener(mFindFragment,
                                                getApplicationContext()));

Again the constructor saves the application context and uses it in toast. In this example an activity passes Context to a handler. Toast and other objects such as Intents, both requires reference to context. That is what drives this example.

class MyTabsListener implements ActionBar.TabListener {
            public Fragment fragment;
            public Context context;
 
            public MyTabsListener(Fragment fragment, Context context) {
                        this.fragment = fragment;
                        this.context = context;
 
            }
 
            @Override
            public void onTabSelected(Tab tab, FragmentTransaction ft) {
                        Toast.makeText(context, "Selected!", Toast.LENGTH_SHORT).show();
                        ft.replace(R.id.fragment_container, fragment);
            }

Which context to use?

There are two types of Context as discussed before.

Application context is associated with the application and will always be same throughout the life of application -- it does not change. So if you are using Toast, you can use application context or even activity context (both) because toast can be displayed from anywhere with in your application and is not attached to a specific window. In the example above we simply want the handler display toast so application context makes sense. But there are many exceptions, one exception is when you need to use or pass the activity context.

Activity context is associated with to the activity and can be destroyed if the activity is destroyed -- there may be multiple activities (more than likely) with a single application. And sometimes you absolutely need the activity context handle. For example, should you launch a new activity, you need to use activity context in its Intent so that the new launching activity is connected to the current activity in terms of activity stack. However, you may use application's context too to launch a new activity but then you need to set flag Intent.FLAG_ACTIVITY_NEW_TASK in intent to treat it as a new task.

So now we have discussed that let's consider some cases:

MainActivity.this refers to the MainActivity context which extends Activity class but the base class (activity) also extends Context class, so it can be used to offer activity context.

getBaseContext() offers activity context.

getApplication() offers application context.

getApplicationContext() also offers application context.

Because various components can run inside the context of an app, activity it is sometimes not clear how to get a hold of the context. For example, assume an activity starts up a fragment and you want to use toast inside the fragment - in this case use getActivity() which returns the activity associated with the fragment (see example code below for a ListFragment).

            public static class TitlesFragment extends ListFragment {
                        boolean mDualPane;
                        int mCurCheckPosition = 0;
 
                        @Override
                        public void onActivityCreated(Bundle savedInstanceState) {
                                    super.onActivityCreated(savedInstanceState);
 
                                    // You can use getActivity(), which returns the activity associated
                                    // with a fragment.
                                    // The activity is a context (since Activity extends Context) .
 
                                    Toast.makeText(getActivity(), "TitlesFragment", Toast.LENGTH_SHORT)
                                                            .show();