Chapter    8

Fragments

So far, we’ve explored several bits and pieces of an Android application, and you’ve run some simple applications tailored to a smartphone-sized screen. All you had to think about was how to lay out the UI controls on the screen for an activity, and how one activity flowed to the next, and so on. For the first two major releases of Android, small screens were it. Then came the Android tablets: devices with screen sizes of 10". And that complicated things. Why? Because now there was so much screen real estate that a simple activity had a hard time filling a screen while at the same time keeping to a single function. It no longer made sense to have an e-mail application that showed only headers in one activity (filling a large screen), and a separate activity to show an individual e-mail (also filling a large screen). With that much room to work with, an application could show a list of e-mail headers down the left side of the screen and the selected e-mail contents on the right side of the screen. Could it be done in a single activity with a single layout? Well, yes, but you couldn’t reuse that activity or layout for any of the smaller-screen devices.

One of the core classes introduced in Android 3.0 was the Fragment class, especially designed to help developers manage application functionality so it would provide great usability as well as lots of reuse. This chapter will introduce you to the fragment, what it is, how it fits into an application’s architecture, and how to use it. Fragments make a lot of interesting things possible that were difficult before. At about the same time, Google released a fragment SDK that works on old Androids. So even if you weren’t interested in writing applications for tablets, you may have found that fragments made your life easier on non-tablet devices. Now it’s easier than ever to write great applications for smartphones and tablets and even TVs and other devices.

Let’s get started with Android fragments.

What Is a Fragment?

This first section will explain what a fragment is and what it does. But first, let’s set the stage to see why we need fragments. As you learned earlier, an Android application on small-screen devices uses activities to show data and functionality to a user, and each activity has a fairly simple, well-defined purpose. For example, an activity might show the user a list of contacts from their address book. Another activity might allow the user to type an e-mail. The Android application is the series of these activities grouped together to achieve a larger purpose, such as managing an e-mail account via the reading and sending of messages. This is fine for a small-screen device, but when the user’s screen is very large (10" or larger), there’s room on the screen to do more than just one simple thing. An application might want to let the user view the list of e-mails in their inbox and at the same time show the currently selected e-mail text next to the list. Or an application might want to show a list of contacts and at the same time show the currently selected contact in a detail view.

As an Android developer, you know that this functionality could be accomplished by defining yet another layout for the xlarge screen with ListViews and layouts and all sorts of other views. And by “yet another layout” we mean layouts in addition to those you’ve probably already defined for the smaller screens. Of course, you’ll want to have separate layouts for the portrait case as well as the landscape case. And with the size of an xlarge screen, this could mean quite a few views for all the labels and fields and images and so on that you’ll need to lay out and then provide code for. If only there were a way to group these view objects together and consolidate the logic for them, so that chunks of an application could be reused across screen sizes and devices, minimizing how much work a developer has to do to maintain their application. And that is why we have fragments.

One way to think of a fragment is as a sub-activity. And in fact, the semantics of a fragment are a lot like an activity. A fragment can have a view hierarchy associated with it, and it has a life cycle much like an activity’s life cycle. Fragments can even respond to the Back button like activities do. If you were thinking, “If only I could put multiple activities together on a tablet’s screen at the same time,” then you’re on the right track. But because it would be too messy to have more than one activity of an application active at the same time on a tablet screen, fragments were created to implement basically that thought. This means fragments are contained within an activity. Fragments can only exist within the context of an activity; you can’t use a fragment without an activity. Fragments can coexist with other elements of an activity, which means you do not need to convert the entire user interface of your activity to use fragments. You can create an activity’s layout as before and only use a fragment for one piece of the user interface.

Fragments are not like activities, however, when it comes to saving state and restoring it later. The fragments framework provides several features to make saving and restoring fragments much simpler than the work you need to do on activities.

How you decide when to use a fragment depends on a few considerations, which are discussed next.

When to Use Fragments

One of the primary reasons to use a fragment is so you can reuse a chunk of user interface and functionality across devices and screen sizes. This is especially true with tablets. Think of how much can happen when the screen is as large as a tablet’s. It’s more like a desktop than a phone, and many of your desktop applications have a multipane user interface. As described earlier, you can have a list and a detail view of the selected item on screen at the same time. This is easy to picture in a landscape orientation with the list on the left and the details on the right. But what if the user rotates the device to portrait mode so that now the screen is taller than it is wide? Perhaps you now want the list to be in the top portion of the screen and the details in the bottom portion. But what if this application is running on a small screen and there’s just no room for the two portions to be on the screen at the same time? Wouldn’t you want the separate activities for the list and for the details to be able to share the logic you’ve built into these portions for a large screen? We hope you answered yes. Fragments can help with that. Figure 8-1 makes this a little clearer.

9781430246800_Fig08-01.jpg

Figure 8-1. Fragments used for a tablet UI and for a smartphone UI

In landscape mode, two fragments may sit nicely side by side. In portrait mode, we might be able to put one fragment above the other. But if we’re trying to run the same application on a device with a smaller screen, we might need to show either fragment 1 or fragment 2 but not both at the same time. If we tried to manage all these scenarios with layouts, we’d be creating quite a few, which means difficulty trying to keep everything correct across many separate layouts. When using fragments, our layouts stay simple; each activity layout deals with the fragments as containers, and the activity layouts don’t need to specify the internal structure of each fragment. Each fragment will have its own layout for its internal structure and can be reused across many configurations.

Let’s go back to the rotating orientation example. If you’ve had to code for orientation changes of an activity, you know that it can be a real pain to save the current state of the activity and to restore the state once the activity has been re-created. Wouldn’t it be nice if your activity had chunks that could be easily retained across orientation changes, so you could avoid all the tearing down and re-creating every time the orientation changed? Of course it would. Fragments can help with that.

Now imagine that a user is in your activity, and they’ve been doing some work. And imagine that the user interface has changed within the same activity, and the user wants to go back a step, or two, or three. In an old-style activity, pressing the Back button will take the user out of the activity entirely. With fragments, the Back button can step backward through a stack of fragments while staying inside the current activity.

Next, think about an activity’s user interface when a big chunk of content changes; you’d like to make the transition look smooth, like a polished application. Fragments can do that, too.

Now that you have some idea of what a fragment is and why you’d want to use one, let’s dig a little deeper into the structure of a fragment.

The Structure of a Fragment

As mentioned, a fragment is like a sub-activity: it has a fairly specific purpose and almost always displays a user interface. But where an activity is subclassed from Context, a fragment is extended from Object in package android.app. A fragment is not an extension of Activity. Like activities, however, you will always extend Fragment (or one of its subclasses) so you can override its behavior.

A fragment can have a view hierarchy to engage with a user. This view hierarchy is like any other view hierarchy in that it can be created (inflated) from an XML layout specification or created in code. The view hierarchy needs to be attached to the view hierarchy of the surrounding activity if it is to be seen by the user, which you’ll get to shortly. The view objects that make up a fragment’s view hierarchy are the same sorts of views that are used elsewhere in Android. So everything you know about views applies to fragments as well.

Besides the view hierarchy, a fragment has a bundle that serves as its initialization arguments. Similar to an activity, a fragment can be saved and later restored automatically by the system. When the system restores a fragment, it calls the default constructor (with no arguments) and then restores this bundle of arguments to the newly created fragment. Subsequent callbacks on the fragment have access to these arguments and can use them to get the fragment back to its previous state. For this reason, it is imperative that you

  • Ensure that there’s a default constructor for your fragment class.
  • Add a bundle of arguments as soon as you create a new fragment so these subsequent methods can properly set up your fragment, and so the system can restore your fragment properly when necessary.

An activity can have multiple fragments in play at one time; and if a fragment has been switched out with another fragment, the fragment-switching transaction can be saved on a back stack. The back stack is managed by the fragment manager tied to the activity. The back stack is how the Back button behavior is managed. The fragment manager is discussed later in this chapter. What you need to know here is that a fragment knows which activity it is tied to, and from there it can get to its fragment manager. A fragment can also get to the activity’s resources through its activity.

Also similar to an activity, a fragment can save state into a bundle object when the fragment is being re-created, and this bundle object gets given back to the fragment’s onCreate() callback. This saved bundle is also passed to onInflate(), onCreateView(), and onActivityCreated(). Note that this is not the same bundle as the one attached as initialization arguments. This bundle is one in which you are likely to store the current state of the fragment, not the values that should be used to initialize it.

A Fragment’s Life Cycle

Before you start using fragments in sample applications, you need understand the life cycle of a fragment. Why? A fragment’s life cycle is more complicated than an activity’s life cycle, and it’s very important to understand when you can do things with fragments. Figure 8-2 shows the life cycle of a fragment.

9781430246800_Fig08-02.jpg

Figure 8-2. Life cycle of a fragment

If you compare this to Figure 2-3 (the life cycle for an activity), you’ll notice several differences, due mostly to the interaction required between an activity and a fragment. A fragment is very dependent on the activity in which it lives and can go through multiple steps while its activity goes through one.

At the very beginning, a fragment is instantiated. It now exists as an object in memory. The first thing that is likely to happen is that initialization arguments will be added to your fragment object. This is definitely true in the situation where the system is re-creating your fragment from a saved state. When the system is restoring a fragment from a saved state, the default constructor is invoked, followed by the attachment of the initialization arguments bundle. If you are doing the creation of the fragment in code, a nice pattern to use is that in Listing 8-1, which shows a factory type of instantiator within the MyFragment class definition.

Listing 8-1. Instantiating a Fragment Using a Static Factory Method

public static MyFragment newInstance(int index) {
    MyFragment f = new MyFragment();
    Bundle args = new Bundle();
    args.putInt("index", index);
    f.setArguments(args);
    return f;
}

From the client’s point of view, they get a new instance by calling the static newInstance() method with a single argument. They get the instantiated object back, and the initialization argument has been set on this fragment in the arguments bundle. If this fragment is saved and reconstructed later, the system will go through a very similar process of calling the default constructor and then reattaching the initialization arguments. For your particular case, you would define the signature of your newInstance() method (or methods) to take the appropriate number and type of arguments, and then build the arguments bundle appropriately. This is all you want your newInstance() method to do. The callbacks that follow will take care of the rest of the setup of your fragment.

The onInflate() Callback

The next thing that happens is layout view inflation. If your fragment is defined by a <fragment> tag in a layout, your fragment’s onInflate() callback will be called. This passes in a reference to the surrounding activity, an AttributeSet with the attributes from the <fragment> tag, and a saved bundle. The saved bundle is the one with the saved state values in it, put there by onSaveInstanceState() if this fragment existed before and is being re-created. The expectation of onInflate() is that you’ll read attribute values and save them for later use. At this stage in the fragment’s life, it’s too early to actually do anything with the user interface. The fragment is not even associated to its activity yet. But that’s the next event to occur to your fragment.

The onAttach() Callback

The onAttach() callback is invoked after your fragment is associated with its activity. The activity reference is passed to you if you want to use it. You can at least use the activity to determine information about your enclosing activity. You can also use the activity as a context to do other operations. One thing to note is that the Fragment class has a getActivity() method that will always return the attached activity for your fragment should you need it. Keep in mind that all during this life cycle, the initialization arguments bundle is available to you from the fragment’s getArguments() method. However, once the fragment is attached to its activity, you can’t call setArguments() again. Therefore, you can’t add to the initialization arguments except in the very beginning.

The onCreate() Callback

Next up is the onCreate() callback. Although this is similar to the activity’s onCreate(), the difference is that you should not put code in here that relies on the existence of the activity’s view hierarchy. Your fragment may be associated to its activity by now, but you haven’t yet been notified that the activity’s onCreate() has finished. That’s coming up. This callback gets the saved state bundle passed in, if there is one. This callback is about as early as possible to create a background thread to get data that this fragment will need. Your fragment code is running on the UI thread, and you don’t want to do disk input/output (I/O) or network accesses on the UI thread. In fact, it makes a lot of sense to fire off a background thread to get things ready. Your background thread is where blocking calls should be. You’ll need to hook up with the data later, perhaps using a handler or some other technique.

Note  One of the ways to load data in a background thread is to use the Loader class. This will be covered in Chapter 28.

The onCreateView() Callback

The next callback is onCreateView(). The expectation here is that you will return a view hierarchy for this fragment. The arguments passed into this callback include a LayoutInflater (which you can use to inflate a layout for this fragment), a ViewGroup parent (called container in Listing 8-2), and the saved bundle if one exists. It is very important to note that you should not attach the view hierarchy to the ViewGroup parent passed in. That association will happen automatically later. You will very likely get exceptions if you attach the fragment’s view hierarchy to the parent in this callback—or at least odd and unexpected application behavior.

Listing 8-2. Creating a Fragment View Hierarchy in onCreateView()

@Override
public View onCreateView(LayoutInflater inflater,
                  ViewGroup container, Bundle savedInstanceState) {
        if(container == null)
            return null;

        View v = inflater.inflate(R.layout.details, container, false);
        TextView text1 = (TextView) v.findViewById(R.id.text1);
        text1.setText(myDataSet[ getPosition() ] );
        return v;
}

The parent is provided so you can use it with the inflate() method of the LayoutInflater. If the parent container value is null, that means this particular fragment won’t be viewed because there’s no view hierarchy for it to attach to. In this case, you can simply return null from here. Remember that there may be fragments floating around in your application that aren’t being displayed. Listing 8-2 shows a sample of what you might want to do in this method.

Here you see how you can access a layout XML file that is just for this fragment and inflate it to a view that you return to the caller. There are several advantages to this approach. You could always construct the view hierarchy in code, but by inflating a layout XML file, you’re taking advantage of the system’s resource-finding logic. Depending on which configuration the device is in, or for that matter which device you’re on, the appropriate layout XML file will be chosen. You can then access a particular view within the layout—in this case, the text1 TextView field—to do what you want with. To repeat a very important point: do not attach the fragment’s view to the container parent in this callback. You can see in Listing 8-2 that you use a container in the call to inflate(), but you also pass false for the attachToRoot parameter.

The onViewCreated() Callback

This one is called right after onCreateView() but before any saved state has been put into the UI. The view object passed in is the same view object that got returned from onCreateView().

The onActivityCreated() Callback

You’re now getting close to the point where the user can interact with your fragment. The next callback is onActivityCreated(). This is called after the activity has completed its onCreate() callback. You can now trust that the activity’s view hierarchy, including your own view hierarchy if you returned one earlier, is ready and available. This is where you can do final tweaks to the user interface before the user sees it. It’s also where you can be sure that any other fragment for this activity has been attached to your activity.

The onViewStateRestored() Callback

This one is relatively new, introduced with JellyBean 4.2. Your fragment will have this callback called when the view hierarchy of this fragment has all state restored (if applicable). Previously you had to make decisions in onActivityCreated() about tweaking the UI for a restored fragment. Now you can put that logic in this callback knowing definitely that this fragment is being restored from a saved state.

The onStart() Callback

The next callback in your fragment life cycle is onStart(). Now your fragment is visible to the user. But you haven’t started interacting with the user just yet. This callback is tied to the activity’s onStart(). As such, whereas previously you may have put your logic into the activity’s onStart(), now you’re more likely to put your logic into the fragment’s onStart(), because that is also where the user interface components are.

The onResume() Callback

The last callback before the user can interact with your fragment is onResume(). This callback is tied to the activity’s onResume(). When this callback returns, the user is free to interact with this fragment. For example, if you have a camera preview in your fragment, you would probably enable it in the fragment’s onResume().

So now you’ve reached the point where the app is busily making the user happy. And then the user decides to get out of your app, either by Back’ing out, or by pressing the Home button, or by launching some other application. The next sequence, similar to what happens with an activity, goes in the opposite direction of setting up the fragment for interaction.

The onPause() Callback

The first undo callback on a fragment is onPause(). This callback is tied to the activity’s onPause(); just as with an activity, if you have a media player in your fragment or some other shared object, you could pause it, stop it, or give it back via your onPause() method. The same good-citizen rules apply here: you don’t want to be playing audio if the user is taking a phone call.

The onSaveInstanceState() Callback

Similar to activities, fragments have an opportunity to save state for later reconstruction. This callback passes in a Bundle object for this fragment to be used as the container for whatever state information you want to hang onto. This is the saved-state bundle passed to the callbacks covered earlier. To prevent memory problems, be careful about what you save into this bundle. Only save what you need. If you need to keep a reference to another fragment, don’t try to save or put the other fragment, rather just save the identifier for the other fragment such as its tag or ID. When this fragment runs onViewStateRestored(), then you could re-establish connections to the other fragments that this fragment depends on.

Although you may see this method usually called right after onPause(), the activity to which this fragment belongs calls it when it feels that the fragment’s state should be saved. This can occur any time before onDestroy().

The onStop() Callback

The next undo callback is onStop(). This one is tied to the activity’s onStop() and serves a purpose similar to an activity’s onStop(). A fragment that has been stopped could go straight back to the onStart() callback, which then leads to onResume().

The onDestroyView() Callback

If your fragment is on its way to being killed off or saved, the next callback in the undo direction is onDestroyView(). This will be called after the view hierarchy you created on your onCreateView() callback earlier has been detached from your fragment.

The onDestroy() Callback

Next up is onDestroy(). This is called when the fragment is no longer in use. Note that it is still attached to the activity and is still findable, but it can’t do much.

The onDetach() Callback

The final callback in a fragment’s life cycle is onDetach(). Once this is invoked, the fragment is not tied to its activity, it does not have a view hierarchy anymore, and all its resources should have been released.

Using setRetainInstance()

You may have noticed the dotted lines in the diagram in Figure 8-2. One of the cool features of a fragment is that you can specify that you don’t want the fragment completely destroyed if the activity is being re-created and therefore your fragments will be coming back also. Therefore, Fragment comes with a method called setRetainInstance(), which takes a boolean parameter to tell it “Yes; I want you to hang around when my activity restarts” or “No; go away, and I’ll create a new fragment from scratch.” A good place to call setRetainInstance() is in the onCreate() callback of a fragment, but in onCreateView() works, as does onActivityCreated().

If the parameter is true, that means you want to keep your fragment object in memory and not start over from scratch. However, if your activity is going away and being re-created, you’ll have to detach your fragment from this activity and attach it to the new one. The bottom line is that if the retain instance value is true, you won’t actually destroy your fragment instance, and therefore you won’t need to create a new one on the other side. The dotted lines on the diagram mean you would skip the onDestroy() callback on the way out, you’d skip the onCreate() callback when your fragment is being re-attached to your new activity, and all other callbacks would fire. Because an activity is re-created most likely for configuration changes, your fragment callbacks should probably assume that the configuration has changed, and therefore should take appropriate action. This would include inflating the layout to create a new view hierarchy in onCreateView(), for example. The code provided in Listing 8-2 would take care of that as it is written. If you choose to use the retain-instance feature, you may decide not to put some of your initialization logic in onCreate() because it won’t always get called the way the other callbacks will.

Sample Fragment App Showing the Life Cycle

There’s nothing like seeing a real example to get an appreciation for a concept. You’ll use a sample application that has been instrumented so you can see all these callbacks in action. You’re going to work with a sample application that uses a list of Shakespearean titles in one fragment; when the user clicks one of the titles, some text from that play will appear in a separate fragment. This sample application will work in both landscape and portrait modes on a tablet. Then you’ll configure it to run as if on a smaller screen so you can see how to separate the text fragment into an activity. You’ll start with the XML layout of your activity in landscape mode in Listing 8-3, which will look like Figure 8-3 when it runs.

Listing 8-3. Your Activity’s Layout XML for Landscape Mode

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is res/layout-land/main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <fragment class="com.androidbook.fragments.bard.TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px"
            android:layout_height="match_parent" />
    <FrameLayout
            android:id="@+id/details" android:layout_weight="2"
            android:layout_width="0px"
            android:layout_height="match_parent" />

</LinearLayout>

9781430246800_Fig08-03.jpg

Figure 8-3. The user interface of your sample fragment application

Note  At the end of the chapter is the URL you can use to download the projects in this chapter. This will allow you to import these projects into your IDE (such as Eclipse or Android Studio) directly.

This layout looks like a lot of other layouts you’ve seen throughout the book, horizontally left to right with two main objects. There’s a special new tag, though, called <fragment>, and this tag has a new attribute called class. Keep in mind that a fragment is not a view, so the layout XML is a little different for a fragment than it is for everything else. The other thing to keep in mind is that the <fragment> tag is just a placeholder in this layout. You should not put child tags under <fragment> in a layout XML file.

The other attributes for a fragment look familiar and serve a purpose similar to that for a view. The fragment tag’s class attribute specifies your extended class for the titles of your application. That is, you must extend one of the Android Fragment classes to implement your logic, and the <fragment> tag must know the name of your extended class. A fragment has its own view hierarchy that will be created later by the fragment itself. The next tag is a FrameLayout—not another <fragment> tag. Why is that? We’ll explain in more detail later, but for now, you should be aware that you’re going to be doing some transitions on the text, swapping out one fragment with another. You use the FrameLayout as the view container to hold the current text fragment. With your titles fragment, you have one—and only one—fragment to worry about: no swapping and no transitions. For the area that displays the Shakespearean text, you’ll have several fragments.

The MainActivity Java code is in Listing 8-4. Actually, the listing only shows the interesting code. The code is instrumented with logging messages so you can see what’s going on through LogCat. Please review the source code files for ShakespeareInstrumented from the web site to see all of it.

Listing 8-4. Interesting Source Code from MainActivity

public boolean isMultiPane() {
    return getResources().getConfiguration().orientation
            == Configuration.ORIENTATION_LANDSCAPE;
}

/**
 * Helper function to show the details of a selected item, either by
 * displaying a fragment in-place in the current UI, or starting a
 * whole new activity in which it is displayed.
 */
public void showDetails(int index) {
    Log.v(TAG, "in MainActivity showDetails(" + index + ")");

    if (isMultiPane()) {
        // Check what fragment is shown, replace if needed.
        DetailsFragment details = (DetailsFragment)
                getFragmentManager().findFragmentById(R.id.details);
        if ( (details == null) ||
             (details.getShownIndex() != index) ) {
            // Make new fragment to show this selection.
            details = DetailsFragment.newInstance(index);

            // Execute a transaction, replacing any existing
            // fragment with this one inside the frame.
            Log.v(TAG, "about to run FragmentTransaction...");
            FragmentTransaction ft
                    = getFragmentManager().beginTransaction();
            ft.setTransition(
                    FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            //ft.addToBackStack("details");
            ft.replace(R.id.details, details);
            ft.commit();
        }

    } else {
        // Otherwise you need to launch a new activity to display
        // the dialog fragment with selected text.
        Intent intent = new Intent();
        intent.setClass(this, DetailsActivity.class);
        intent.putExtra("index", index);
        startActivity(intent);
    }
}

This is a very simple activity to write. To determine multipane mode (that is, whether you need to use fragments side by side), you just use the orientation of the device. If you’re in landscape mode, you’re multipane; if you’re in portrait mode, you’re not. The helper method showDetails() is there to figure out how to show the text when a title is selected. The index is the position of the title in the title list. If you’re in multipane mode, you’re going to use a fragment to show the text. You’re calling this fragment a DetailsFragment, and you use a factory-type method to create one with the index. The interesting code for the DetailsFragment class is shown in Listing 8-5 (minus all of the logging code). As we did before in TitlesFragment, the various callbacks of DetailsFragment have logging added so we can watch what happens via LogCat. You’ll come back to your showDetails() method later.

Listing 8-5. Source Code for DetailsFragment

public class DetailsFragment extends Fragment {

    private int mIndex = 0;

    public static DetailsFragment newInstance(int index) {
        Log.v(MainActivity.TAG, "in DetailsFragment newInstance(" +
                                 index + ")");

        DetailsFragment df = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        df.setArguments(args);
        return df;
    }

    public static DetailsFragment newInstance(Bundle bundle) {
        int index = bundle.getInt("index", 0);
        return newInstance(index);
    }

    @Override
    public void onCreate(Bundle myBundle) {
        Log.v(MainActivity.TAG,
                "in DetailsFragment onCreate. Bundle contains:");
        if(myBundle != null) {
            for(String key : myBundle.keySet()) {
                Log.v(MainActivity.TAG, "    " + key);
            }
        }
        else {
            Log.v(MainActivity.TAG, "    myBundle is null");
        }
        super.onCreate(myBundle);

        mIndex = getArguments().getInt("index", 0);
    }

    public int getShownIndex() {
        return mIndex;
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
            ViewGroup container, Bundle savedInstanceState) {
        Log.v(MainActivity.TAG,
                "in DetailsFragment onCreateView. container = " +
                container);

        // Don't tie this fragment to anything through the inflater.
        // Android takes care of attaching fragments for us. The
        // container is only passed in so you can know about the
        // container where this View hierarchy is going to go.
        View v = inflater.inflate(R.layout.details, container, false);
        TextView text1 = (TextView) v.findViewById(R.id.text1);
        text1.setText(Shakespeare.DIALOGUE[ mIndex ] );
        return v;
    }
}

The DetailsFragment class is actually fairly simple as well. Now you can see how to instantiate this fragment. It’s important to point out that you’re instantiating this fragment in code because your layout defines the ViewGroup container (a FrameLayout) that your details fragment is going to go into. Because the fragment is not itself defined in the layout XML for the activity, as your titles fragment was, you need to instantiate your details fragments in code.

To create a new details fragment, you use your newInstance() method. As discussed earlier, this factory method invokes the default constructor and then sets the arguments bundle with the value of index. Once newInstance() has run, your details fragment can retrieve the value of index in any of its callbacks by referring to the arguments bundle via getArguments(). For your convenience, in onCreate() you can save the index value from the arguments bundle to a member field in your DetailsFragment class.

You might wonder why you didn’t simply set the mIndex value in newInstance(). The reason is that Android will, behind the scenes, re-create your fragment using the default constructor. Then it sets the arguments bundle to what it was before. Android won’t use your newInstance() method, so the only reliable way to ensure that mIndex is set is to read the value from the arguments bundle and set it in onCreate(). The convenience method getShownIndex() retrieves the value of that index. Now the only method left to describe in the details fragment is onCreateView(). And this is very simple, too.

The purpose of onCreateView() is to return the view hierarchy for your fragment. Remember that based on your configuration, you could want all kinds of different layouts for this fragment. Therefore, the most common thing to do is utilize a layout XML file for your fragment. In your sample application, you specify the layout for the fragment to be details.xml using the resource R.layout.details. The XML for details.xml is in Listing 8-6.

Listing 8-6. The details.xml Layout File for the Details Fragment

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is res/layout/details.xml -->
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <ScrollView android:id="@+id/scroller"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
    <TextView android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
  </ScrollView>
</LinearLayout>

For your sample application, you can use the exact same layout file for details whether you’re in landscape mode or in portrait mode. This layout is not for the activity, it’s just for your fragment to display the text. Because it could be considered the default layout, you can store it in the /res/layout directory and it will be found and used even if you’re in landscape mode. When Android goes looking for the details XML file, it tries the specific directories that closely match the device’s configuration, but it will end up in the /res/layout directory if it can’t find the details.xml file in any of the other places. Of course, if you want to have a different layout for your fragment in landscape mode, you could define a separate details.xml layout file and store it under /res/layout-land. Feel free to experiment with different details.xml files.

When your details fragment’s onCreateView() is called, you will simply grab the appropriate details.xml layout file, inflate it, and set the text to the text from the Shakespeare class. The entire Java code for Shakespeare is not shown here, but a portion is in Listing 8-7 so you understand how it was done. For the complete source, access the project download files, as described in the “References” section at the end of this chapter.

Listing 8-7. Source Code for Shakespeare.java

public class Shakespeare {
    public static String TITLES[] = {
            "Henry IV (1)",
            "Henry V",
            "Henry VIII",
            "Romeo and Juliet",
            "Hamlet",
            "The Merchant of Venice",
            "Othello"
    };
    public static String DIALOGUE[] = {
        "So shaken as we are, so wan with care, ...
... and so on ...

Now your details fragment view hierarchy contains the text from the selected title. Your details fragment is ready to go. And you can return to MainActivity’s showDetails() method to talk about FragmentTransactions.

FragmentTransactions and the Fragment Back Stack

The code in showDetails() that pulls in your new details fragment (partially shown again in Listing 8-8) looks rather simple, but there’s a lot going on here. It’s worth spending some time to explain what is happening and why. If your activity is in multipane mode, you want to show the details in a fragment next to the title list. You may already be showing details, which means you may have a details fragment visible to the user. Either way, the resource ID R.id.details is for the FrameLayout for your activity, as shown in Listing 8-3. If you have a details fragment sitting in the layout because you didn’t assign any other ID to it, it will have this ID. Therefore, to find out if there’s a details fragment in the layout, you can ask the fragment manager using findFragmentById(). This will return null if the frame layout is empty or will give you the current details fragment. You can then decide if you need to place a new details fragment in the layout, either because the layout is empty or because there’s a details fragment for some other title. Once you make the determination to create and use a new details fragment, you invoke the factory method to create a new instance of a details fragment. Now you can put this new fragment into place for the user to see.

Listing 8-8. Fragment Transaction Example

public void showDetails(int index) {
    Log.v(TAG, "in MainActivity showDetails(" + index + ")");

    if (isMultiPane()) {
        // Check what fragment is shown, replace if needed.
        DetailsFragment details = (DetailsFragment)
                getFragmentManager().findFragmentById(R.id.details);
        if (details == null || details.getShownIndex() != index) {
            // Make new fragment to show this selection.
            details = DetailsFragment.newInstance(index);

            // Execute a transaction, replacing any existing
            // fragment with this one inside the frame.
            Log.v(TAG, "about to run FragmentTransaction...");
            FragmentTransaction ft
                    = getFragmentManager().beginTransaction();
            ft.setTransition(
                    FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            //ft.addToBackStack("details");
            ft.replace(R.id.details, details);
            ft.commit();
        }
            // The rest was left out to save space.
}

A key concept to understand is that a fragment must live inside a view container, also known as a view group. The ViewGroup class includes such things as layouts and their derived classes. FrameLayout is a good choice as the container for the details fragment in the main.xml layout file of your activity. A FrameLayout is simple, and all you need is a simple container for your fragment, without the extra baggage that comes with other types of layouts. The FrameLayout is where your details fragment is going to go. If you had instead specified another <fragment> tag in the activity’s layout file instead of a FrameLayout, you would not be able to replace the current fragment with a new fragment (i.e., swap fragments).

The FragmentTransaction is what you use to do your swapping. You tell the fragment transaction that you want to replace whatever is in your frame layout with your new details fragment. You could have avoided all this by locating the resource ID of the details TextView and just setting the text of it to the new text for the new Shakespeare title. But there’s another side to fragments that explains why you use FragmentTransactions.

As you know, activities are arranged in a stack, and as you get deeper and deeper into an application, it’s not uncommon to have a stack of several activities going at once. When you press the Back button, the topmost activity goes away, and you are returned to the activity below, which resumes for you. This can continue until you’re at the home screen again.

This was fine when an activity was just single-purpose, but now that an activity can have several fragments going at once, and because you can go deeper into your application without leaving the topmost activity, Android really needed to extend the Back button stack concept to include fragments as well. In fact, fragments demand this even more. When there are several fragments interacting with each other at the same time in an activity, and there’s a transition to new content across several fragments at once, pressing the Back button should cause each of the fragments to roll back one step together. To ensure that each fragment properly participates in the rollback, a FragmentTransaction is created and managed to perform that coordination.

Be aware that a back stack for fragments is not required within an activity. You can code your application to let the Back button work at the activity level and not at the fragment level at all. If there’s no back stack for your fragments, pressing the Back button will pop the current activity off the stack and return the user to whatever was underneath. If you choose to take advantage of the back stack for fragments, you will want to uncomment in Listing 8-8 the line that says ft.addToBackStack("details"). For this particular case, you’ve hardcoded the tag parameter to be the string "details". This tag should be an appropriate string name that represents the state of the fragments at the time of the transaction. The tag is not necessarily a name for a specific fragment but rather for the fragment transaction and all the fragments in the transaction. You will be able to interrogate the back stack in code using the tag value to delete entries, as well as pop entries off. You will want meaningful tags on these transactions to be able to find the appropriate ones later.

Fragment Transaction Transitions and Animations

One of the very nice things about fragment transactions is that you can perform transitions from an old fragment to a new fragment using transitions and animations. These are not like the animations coming later, in Chapter 18. These are much simpler and do not require in-depth graphics knowledge. Let’s use a fragment transaction transition to add special effects when you swap out the old details fragment with a new details fragment. This can add polish to your application, making the switch from the old to the new fragment look smooth.

One method to accomplish this is setTransition(), as shown in Listing 8-8. However, there are a few different transitions available. You used a fade in your example, but you can also use the setCustomAnimations() method to describe other special effects, such as sliding one fragment out to the right as another slides in from the left. The custom animations use the new object animation definitions, not the old ones. The old anim XML files use tags such as <translate>, whereas the new XML files use <objectAnimator>. The old standard XML files are located in the /data/res/anim directory under the appropriate Android SDK platforms directory (such as platforms/android-11 for Honeycomb). There are some new XML files located in the /data/res/animator directory here, too. Your code could be something like

ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);

which will cause the new fragment to fade in as the old fragment fades out. The first parameter applies to the fragment entering, and the second parameter applies to the fragment exiting. Feel free to explore the Android animator directory for more stock animations. If you’d like to create your own, there’s section on the object animator in Chapter 18 to help you. The other very important bit of knowledge you need is that the transition calls need to come before the replace() call; otherwise, they will have no effect.

Using the object animator for special effects on fragments can be a fun way to do transitions. There are two other methods on FragmentTransaction you should know about: hide() and show(). Both of these methods take a fragment as a parameter, and they do exactly what you’d expect. For a fragment in the fragment manager associated to a view container, the methods simply hide or show the fragment in the user interface. The fragment does not get removed from the fragment manager in the process, but it certainly must be tied into a view container in order to affect its visibility. If a fragment does not have a view hierarchy, or if its view hierarchy is not tied into the displayed view hierarchy, then these methods won’t do anything.

Once you’ve specified the special effects for your fragment transaction, you have to tell it the main work that you want done. In your case, you’re replacing whatever is in the frame layout with your new details fragment. That’s where the replace() method comes in. This is equivalent to calling remove() for any fragments that are already in the frame layout and then add() for your new details fragment, which means you could just call remove() or add() as needed instead.

The final action you must take when working with a fragment transaction is to commit it. The commit() method does not cause things to happen immediately but rather schedules the work for when the UI thread is ready to do it.

Now you should understand why you need to go to so much trouble to change the content in a simple fragment. It’s not just that you want to change the text; you might want a special graphics effect during the transition. You may also want to save the transition details in a fragment transaction that you can reverse later. That last point may be confusing, so we’ll clarify.

This is not a transaction in the truest sense of the word. When you pop fragment transactions off the back stack, you are not undoing all the data changes that may have taken place. If data changed within your activity, for example, as you created fragment transactions on the back stack, pressing the Back button does not cause the activity data changes to revert back to their previous values. You are merely stepping back through the user interface views the way you came in, just as you do with activities, but in this case it’s for fragments. Because of the way fragments are saved and restored, the inner state of a fragment that has been restored from a saved state will depend on what values you saved with the fragment and how you manage to restore them. So your fragments may look the same as they did previously but your activity will not, unless you take steps to restore activity state when you restore fragments.

In your example, you’re only working with one view container and bringing in one details fragment. If your user interface were more complicated, you could manipulate other fragments within the fragment transaction. What you are actually doing is beginning the transaction, replacing any existing fragment in your details frame layout with your new details fragment, specifying a fade-in animation, and committing the transaction. You commented out the part where this transaction is added to the back stack, but you could certainly uncomment it to take part in the back stack.

The FragmentManager

The FragmentManager is a component that takes care of the fragments belonging to an activity. This includes fragments on the back stack and fragments that may just be hanging around. We’ll explain.

Fragments should only be created within the context of an activity. This occurs either through the inflation of an activity’s layout XML or through direct instantiation using code like that in Listing 8-1. When instantiated through code, a fragment usually gets attached to the activity using a fragment transaction. In either case, the FragmentManager class is used to access and manage these fragments for an activity.

You use the getFragmentManager() method on either an activity or an attached fragment to retrieve a fragment manager. You saw in Listing 8-8 that a fragment manager is where you get a fragment transaction. Besides getting a fragment transaction, you can also get a fragment using the fragment’s ID, its tag, or a combination of bundle and key. The fragment’s ID will either be the fragment’s resource ID if the fragment was inflated from XML, or it will be the container’s resource ID if the fragment was placed into a view using a fragment transaction. A fragment’s tag is a String that you can assign in the fragment’s XML definition, or when the fragment is placed in a view via a fragment transaction. The bundle and key method of retrieving a fragment only works for fragments that were persisted using the putFragment() method.

For getting a fragment, the getter methods include findFragmentById(), findFragmentByTag(), and getFragment(). The getFragment() method would be used in conjunction with putFragment(), which also takes a bundle, a key, and the fragment to be put. The bundle is most likely going to be the savedState bundle, and putFragment() will be used in the onSaveInstanceState() callback to save the state of the current activity (or another fragment). The getFragment() method would probably be called in onCreate() to correspond to putFragment(), although for a fragment, the bundle is available to the other callback methods, as described earlier.

Obviously, you can’t use the getFragmentManager() method on a fragment that has not been attached to an activity yet. But it’s also true that you can attach a fragment to an activity without making it visible to the user yet. If you do this, you should associate a String tag to the fragment so you can get to it in the future. You’d most likely use this method of FragmentTransaction to do this:

public FragmentTransaction add (Fragment fragment, String tag)

In fact, you can have a fragment that does not exhibit a view hierarchy. This might be done to encapsulate certain logic together such that it could be attached to an activity, yet still retain some autonomy from the activity’s life cycle and from other fragments. When an activity goes through a re-create cycle due to a device-configuration change, this non-UI fragment could remain largely intact while the activity goes away and comes back again. This would be a good candidate for the setRetainInstance() option.

The fragment back stack is also the domain of the fragment manager. Whereas a fragment transaction is used to put fragments onto the back stack, the fragment manager can take fragments off the back stack. This is usually done using the fragment’s ID or tag, but it can be done based on position in the back stack or just to pop the topmost fragment.

Finally, the fragment manager has methods for some debugging features, such as turning on debugging messages to LogCat using enableDebugLogging() or dumping the current state of the fragment manager to a stream using dump(). Note that you turned on fragment manager debugging in the onCreate() method of your activity in Listing 8-4.

Caution When Referencing Fragments

It’s time to revisit the earlier discussion of the fragment’s life cycle and the arguments and saved-state bundles. Android could save one of your fragments at many different times. This means that at the moment your application wants to retrieve that fragment, it’s possible that it is not in memory. For this reason, we caution you not to think that a variable reference to a fragment is going to remain valid for a long time. If fragments are being replaced in a container view using fragment transactions, any reference to the old fragment is now pointing to a fragment that is possibly on the back stack. Or a fragment may get detached from the activity’s view hierarchy during an application configuration change such as a screen rotation. Be careful.

If you’re going to hold onto a reference to a fragment, be aware of when it could get saved away; when you need to find it again, use one of the getter methods of the fragment manager. If you want to hang onto a fragment reference, such as when an activity is going through a configuration change, you can use the putFragment() method with the appropriate bundle. In the case of both activities and fragments, the appropriate bundle is the savedState bundle that is used in onSaveInstanceState() and that reappears in onCreate() (or, in the case of fragments, the other early callbacks of the fragment’s life cycle). You will probably never store a direct fragment reference into the arguments bundle of a fragment; if you’re tempted to do so, please think very carefully about it first.

The other way you can get to a specific fragment is by querying for it using a known tag or known ID. The getter methods described previously will allow retrieval of fragments from the fragment manager this way, which means you have the option of just remembering the tag or ID of a fragment so that you can retrieve it from the fragment manager using one of those values, as opposed to using putFragment() and getFragment().

Saving Fragment State

Another interesting class was introduced in Android 3.2: Fragment.SavedState. Using the saveFragmentInstanceState() method of FragmentManager, you can pass this method a fragment, and it returns an object representing the state of that fragment. You can then use that object when initializing a fragment, using Fragment’s setInitialSavedState() method. Chapter 9 discusses this in more detail.

ListFragments and <fragment>

There are still a few more things to cover to make your sample application complete. The first is the TitlesFragment class. This is the one that is created via the main.xml file of your main activity. The <fragment> tag serves as your placeholder for where this fragment will go and does not define what the view hierarchy will look like for this fragment. The interesting code for your TitlesFragment is in Listing 8-9. For all of the code please refer to the source code files. TitlesFragment displays the list of titles for your application.

Listing 8-9. TitlesFragment Java Code

public class TitlesFragment extends ListFragment {
    private MainActivity myActivity = null;
    int mCurCheckPosition = 0;

    @Override
    public void onAttach(Activity myActivity) {
        Log.v(MainActivity.TAG,
            "in TitlesFragment onAttach; activity is: " + myActivity);
        super.onAttach(myActivity);
        this.myActivity = (MainActivity)myActivity;
    }

    @Override
    public void onActivityCreated(Bundle savedState) {
        Log.v(MainActivity.TAG,
            "in TitlesFragment onActivityCreated. savedState contains:");
        if(savedState != null) {
            for(String key : savedState.keySet()) {
                Log.v(MainActivity.TAG, "    " + key);
            }
        }
        else {
            Log.v(MainActivity.TAG, "    savedState is null");
        }
        super.onActivityCreated(savedState);

        // Populate list with your static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_1,
                Shakespeare.TITLES));

        if (savedState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedState.getInt("curChoice", 0);
        }

        // Get your ListFragment's ListView and update it
        ListView lv = getListView();
        lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        lv.setSelection(mCurCheckPosition);

        // Activity is created, fragments are available
        // Go ahead and populate the details fragment
        myActivity.showDetails(mCurCheckPosition);
    }
    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.v(MainActivity.TAG, "in TitlesFragment onSaveInstanceState");
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int pos, long id) {
        Log.v(MainActivity.TAG,
            "in TitlesFragment onListItemClick. pos = "
            + pos);
        myActivity.showDetails(pos);
        mCurCheckPosition = pos;
    }

    @Override
    public void onDetach() {
        Log.v(MainActivity.TAG, "in TitlesFragment onDetach");
        super.onDetach();
        myActivity = null;
    }
}

Unlike DetailsFragment, for this fragment you don’t do anything in the onCreateView() callback. This is because you’re extending the ListFragment class, which contains a ListView already. The default onCreateView() for a ListFragment creates this ListView for you and returns it. It’s not until onActivityCreated() that you do any real application logic. By this time in your application, you can be sure that the activity’s view hierarchy, plus this fragment’s, has been created. The resource ID for that ListView is android.R.id.list1, but you can always call getListView() if you need to get a reference to it, which you do in onActivityCreated(). Because ListFragment manages the ListView, do not attach the adapter to the ListView directly. You must use the ListFragment’s setListAdapter() method instead. The activity’s view hierarchy is now set up, so you’re safe going back into the activity to do the showDetails() call.

At this point in your sample activity’s life, you’ve added a list adapter to your list view, you’ve restored the current position (if you came back from a restore, due perhaps to a configuration change), and you’ve asked the activity (in showDetails()) to set the text to correspond to the selected Shakespearean title.

Your TitlesFragment class also has a listener on the list so when the user clicks another title, the onListItemClick() callback is called, and you switch the text to correspond to that title, again using the showDetails() method.

Another difference between this fragment and the earlier details fragment is that when this fragment is being destroyed and re-created, you save state in a bundle (the value of the current position in the list), and you read it back in onCreate(). Unlike the details fragments that get swapped in and out of the FrameLayout on your activity’s layout, there is just one titles fragment to think about. So when there is a configuration change and your titles fragment is going through a save-and-restore operation, you want to remember where you were. With the details fragments, you can re-create them without having to remember the previous state.

Invoking a Separate Activity When Needed

There’s a piece of code we haven’t talked about yet, and that is in showDetails() when you’re in portrait mode and the details fragment won’t fit properly on the same page as the titles fragment. If the screen real estate won’t permit feasible viewing of a fragment that would otherwise be shown alongside the other fragments, you will need to launch a separate activity to show the user interface of that fragment. For your sample application, you implement a details activity; the code is in Listing 8-10.

Listing 8-10. Showing a New Activity When a Fragment Doesn’t Fit

public class DetailsActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.v(MainActivity.TAG, "in DetailsActivity onCreate");
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, it means
            // that your MainActivity is being shown with both
            // the titles and the text, so this activity is
            // no longer needed. Bail out and let the MainActivity
            // do all the work.
            finish();
            return;
        }

        if(getIntent() != null) {
            // This is another way to instantiate a details
            // fragment.
            DetailsFragment details =
                DetailsFragment.newInstance(getIntent().getExtras());

            getFragmentManager().beginTransaction()
                .add(android.R.id.content, details)
                .commit();
        }
    }
}

There are several interesting aspects to this code. For one thing, it is really easy to implement. You make a simple determination of the device’s orientation, and as long as you’re in portrait mode, you set up a new details fragment within this details activity. If you’re in landscape mode, your MainActivity is able to display both the titles fragment and the details fragment, so there is no reason to be displaying this activity at all. You may wonder why you would ever launch this activity if you’re in landscape mode, and the answer is, you wouldn’t. However, once this activity has been started in portrait mode, if the user rotates the device to landscape mode, this details activity will get restarted due to the configuration change. So now the activity is starting up, and it’s in landscape mode. At that moment, it makes sense to finish this activity and let the MainActivity take over and do all the work.

Another interesting aspect about this details activity is that you never set the root content view using setContentView(). So how does the user interface get created? If you look carefully at the add() method call on the fragment transaction, you will see that the view container to which you add the fragment is specified as the resource android.R.id.content. This is the top-level view container for an activity, and therefore when you attach your fragment view hierarchy to this container, your fragment view hierarchy becomes the only view hierarchy for the activity. You used the very same DetailsFragment class as before with the other newInstance() method to create the fragment (the one that takes a bundle as a parameter), then you simply attached it to the top of the activity’s view hierarchy. This causes the fragment to be displayed within this new activity.

From the user’s point of view, they are now looking at just the details fragment view, which is the text from the Shakespearean play. If the user wants to select a different title, they press the Back button, which pops this activity to reveal your main activity (with the titles fragment only). The other choice for the user is to rotate the device to get back to landscape mode. Then your details activity will call finish() and go away, revealing the also-rotated main activity underneath.

When the device is in portrait mode, if you’re not showing the details fragment in your main activity, you should have a separate main.xml layout file for portrait mode like the one in Listing 8-11.

Listing 8-11. The Layout for a Portrait Main Activity

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is res/layout/main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <fragment class="com.androidbook.fragments.bard.TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
</LinearLayout>

Of course, you could make this layout whatever you want it to be. For your purposes here, you simply make it show the titles fragment by itself. It’s very nice that your titles fragment class doesn’t need to include much code to deal with the device reconfiguration.

Take a moment to view this application’s manifest file. In it you find the main activity with a category of LAUNCHER so that it will appear in the device’s list of apps. Then you have the separate DetailsActivity with a category of DEFAULT. This allows you to start the details activity from code but will not show the details activity as an app in the App list.

Persistence of Fragments

When you play with this sample application, make sure you rotate the device (pressing Ctrl+F11 rotates the device in the emulator). You will see that the device rotates, and the fragments rotate right along with it. If you watch the LogCat messages, you will see a lot of them for this application. In particular, during a device rotation, pay careful attention to the messages about fragments; not only does the activity get destroyed and re-created, but the fragments do also.

So far, you only wrote a tiny bit of code on the titles fragment to remember the current position in the titles list across restarts. You didn’t do anything in the details fragment code to handle reconfigurations, and that’s because you didn’t need to. Android will take care of hanging onto the fragments that are in the fragment manager, saving them away, then restoring them when the activity is being re-created. You should realize that the fragments you get back after the reconfiguration is complete are very likely not the same fragments in memory that you had before. These fragments have been reconstructed for you. Android saved the arguments bundle and the knowledge of which type of fragment it was, and it stored the saved-state bundles for each fragment that contain saved-state information about the fragment to use to restore it on the other side.

The LogCat messages show you the fragments going through their life cycles in sync with the activity. You will see that your details fragment gets re-created, but your newInstance() method does not get called again. Instead, Android uses the default constructor, attaches the arguments bundle to it, and then starts calling the callbacks on the fragment. This is why it is so important not to do anything fancy in the newInstance() method: when the fragment gets re-created, it won’t do it through newInstance().

You should also appreciate by now that you’ve been able to reuse your fragments in a few different places. The titles fragment was used in two different layouts, but if you look at the titles fragment code, it doesn’t worry about the attributes of each layout. You could make the layouts rather different from each other, and the titles fragment code would look the same. The same can be said of the details fragment. It was used in your main landscape layout and within the details activity all by itself. Again, the layout for the details fragment could have been very different between the two, and the code of the details fragment would be the same. The code of the details activity was very simple, also.

So far, you’ve explored two of the fragment types: the base Fragment class and the ListFragment subclass. Fragment has other subclasses: the DialogFragment, PreferenceFragment, and WebViewFragment. We’ll cover DialogFragment and PreferenceFragment in Chapters 10 and 11, respectively.

Communications with Fragments

Because the fragment manager knows about all fragments attached to the current activity, the activity or any fragment in that activity can ask for any other fragment using the getter methods described earlier. Once the fragment reference has been obtained, the activity or fragment could cast the reference appropriately and then call methods directly on that activity or fragment. This would cause your fragments to have more knowledge about the other fragments than might normally be desired, but don’t forget that you’re running this application on a mobile device, so cutting corners can sometimes be justified. A code snippet is provided in Listing 8-12 to show how one fragment might communicate directly with another fragment. The snippet would be part of one of your extended Fragment classes, and FragmentOther is a different extended Fragment class.

Listing 8-12. Direct Fragment-to-Fragment Communication

FragmentOther fragOther =
        (FragmentOther)getFragmentManager().findFragmentByTag("other");
fragOther.callCustomMethod( arg1, arg2 );

In Listing 8-12, the current fragment has direct knowledge of the class of the other fragment and also which methods exist on that class. This may be okay because these fragments are part of one application, and it can be easier to simply accept the fact that some fragments will know about other fragments. We’ll show you a cleaner way to communicate between fragments in the DialogFragment sample application in Chapter 10.

Using startActivity() and setTargetFragment()

A feature of fragments that is very much like activities is the ability of a fragment to start an activity. Fragment has a startActivity() method and startActivityForResult() method. These work just like the ones for activities; when a result is passed back, it will cause the onActivityResult() callback to fire on the fragment that started the activity.

There’s another communication mechanism you should know about. When one fragment wants to start another fragment, there is a feature that lets the calling fragment set its identity with the called fragment. Listing 8-13 shows an example of what it might look like.

Listing 8-13. Fragment-to-Target-Fragment Setup

mCalledFragment = new CalledFragment();
mCalledFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mCalledFragment, "work").commit();

With these few lines, you’ve created a new CalledFragment object, set the target fragment on the called fragment to the current fragment, and added the called fragment to the fragment manager and activity using a fragment transaction. When the called fragment starts to run, it will be able to call getTargetFragment(), which will return a reference to the calling fragment. With this reference, the called fragment could invoke methods on the calling fragment or even access view components directly. For example, in Listing 8-14, the called fragment could set text in the UI of the calling fragment directly.

Listing 8-14. Target Fragment-to-Fragment Communication

TextView tv = (TextView)
    getTargetFragment().getView().findViewById(R.id.text1);
tv.setText("Set from the called fragment");

References

Here are some helpful references to topics you may wish to explore further:

Summary

This chapter introduced the Fragment class and its related classes for the manager, transactions, and subclasses. This is a summary of what’s been covered in this chapter:

  • The Fragment class, what it does, and how to use it.
  • Why fragments cannot be used without being attached to one and only one activity.
  • That although fragments can be instantiated with a static factory method such as newInstance(), you must always have a default constructor and a way to save initialization values into an initialization arguments bundle.
  • The life cycle of a fragment and how it is intertwined with the life cycle of the activity that owns the fragment.
  • FragmentManager and its features.
  • Managing device configurations using fragments.
  • Combining fragments into a single activity, or splitting them between multiple activities.
  • Using fragment transactions to change what’s displayed to a user, and animating those transitions using cool effects.
  • New behaviors that are possible with the Back button when using fragments.
  • Using the <fragment> tag in a layout.
  • Using a FrameLayout as a placeholder for a fragment when you want to use transitions.
  • ListFragment and how to use an adapter to populate the data (very much like a ListView).
  • Launching a new activity when a fragment can’t fit onto the current screen, and how to adjust when a configuration change makes it possible to see multiple fragments again.
  • Communicating between fragments, and between a fragment and its activity.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.167.178