Making the shift to fragments

Although fragments are a very powerful tool, fundamentally they do something very simple. Fragments group user interface components and their associated logic. Creating the portion of your user interface associated with a fragment is very much like doing so for an activity. In most cases, the view hierarchy for a particular fragment is created from a layout resource; although, just as with activities, the view hierarchy can be programmatically generated.

Creating a layout resource for a fragment follows the same rules and techniques as doing so for an activity. The key difference is that we're looking for opportunities to partition our user interface layout into manageable subsections when working with fragments.

The easiest way to get started working with fragments is for us to walk through converting a traditional activity-oriented user interface to use fragments.

To get started, let's first look at the appearance and structure of the application we're going to convert. This application contains a single activity that, when run, looks like the following screenshot:

The old thinking – activity-oriented

The activity displays a list of five book titles in the top portion of the activity. When the user selects one of those books titles, the description of that book appears in the bottom portion of the activity.

The appearance of the activity is defined in a layout resource file named activity_main.xml that contains the following layout description:

This layout resource is reasonably simple and is explained as follows:

The activity-oriented user interface we currently have would be fine if all Android devices had the same form factor. As we've discussed, that's not the case.

We need to partition the application user interface so that we can switch to a fragment-oriented approach. With proper partitioning, we can be ready to make some simple enhancements to our application to help it adapt to device differences.

Let's look at some simple changes we can make that will partition our user interface.

The first step in moving to a fragment-oriented user interface is to identify the natural partitions in the existing user interface. In the case of this application, the natural partitions are reasonably easy to identify. The list of book titles is one good candidate, and the book description is the other. We'll make them each a separate fragment.

For the list of book titles, we have the option to define the fragment to contain either the ScrollView element that's nearest to the top (has an id value of scrollTitles) or just the RadioGroup element within that ScrollView element. When creating a fragment, we want to structure it such that the fragment is most easily reused. Although the RadioGroup element is all we need to display the list of titles, it seems likely that we'll always want the user to be able to scroll the list of titles if necessary. With this being the case, it makes sense to include the ScrollView element in this fragment.

To create a fragment for the book list, we define a new layout resource file called fragment_book_list.xml. We copy the top ScrollView element and its contents from the activity_main.xml resource file to the fragment_book_list.xml resource file. The resulting fragment_book_list.xml resource file is as follows:

This gives us a layout resource consistent with the book title portion of the user interface as it appeared in the activity layout resource. This is a good start.

An effective fragment-oriented user interface is constructed with layout resources that minimize assumptions about where and how the fragment is used. The fewer assumptions we make about a fragment's use, the more reusable the fragment becomes.

The layout in the fragment_book_list.xml resource file as we now have it is very limiting because it includes significant assumptions. For example, the root ScrollView element includes a layout_height attribute with a value of 0. This assumes that the fragment will be placed within a layout that calculates the height for the fragment.

A layout_height attribute value of 0 prevents the ScrollView element from properly rendering when we use the fragment within any of the many layouts that require the ScrollView element to specify a meaningful height. A layout_height attribute value of 0 prevents the fragment from properly rendering even when doing something as simple as placing the fragment within a horizontally oriented LinearLayout element. The layout_weight attribute has similar issues.

In general, a good practice is to design the fragment to fully occupy whatever space it is placed within. This gives the layout in which the fragment is used the most control over the placement and sizing of the fragment.

To do this, we'll remove the layout_weight attribute from the ScrollView element and change the layout_height attribute value to match_parent. Because the ScrollView element is now the root node of the layout resource, we also need to add the android namespace prefix declaration.

The following code snippet shows the updated ScrollView element:

With the updated ScrollView element, the fragment layout can now adapt to almost any layout it's referenced within.

Just like when creating an activity, we need more than a simple layout definition for our fragment; we also need a class.

All fragment classes must extend the android.app.Fragment class either directly or indirectly.

We'll call the class for the fragment that manages the book list, BookListFragment. The class will directly extend the Fragment class as follows:

During the creation of a fragment, the Android framework calls a number of methods on that fragment. One of the most important of these is the onCreateView method. The onCreateView method is responsible for returning the view hierarchy represented by the fragment. The Android framework attaches that returned view hierarchy for the fragment to the appropriate place in the activity's overall view hierarchy.

In a case like the BookListFragment class where the Fragment class inherits directly from the Fragment class, we must override the onCreateView method and perform the work necessary to construct the view hierarchy.

The onCreateView method receives three parameters. We'll focus on just the first two for now:

The LayoutInflater class provides a method called inflate that handles the details of converting a layout resource into the corresponding view hierarchy and returns a reference to the root view of that hierarchy. Using the LayoutInflater.inflate method, we can implement our BookListFragment class' onCreateView method to construct and return the view hierarchy corresponding to the R.layout.fragment_book_list layout resource as shown in the following code:

You'll notice in the preceding code we include the container reference and a Boolean value of false in the call to the inflate method. The container reference provides the necessary layout parameters for the inflate method to properly format the new view hierarchy. The parameter value of false indicates that container is to be used only for the layout parameters. If this value were true, the inflate method would also attach the new view hierarchy to the container view group. We do not want to attach the new view hierarchy to the container view group in the onCreateView method because the activity will handle that.

With the fragments defined, we can now update the activity to use them. To get started, we'll remove all the book titles and description layout information from the activity_main.xml layout resource file. The file now contains just the top-level LinearLayout element and comments to show where the book titles and description belong as follows:

Using the fragment element, we can add a fragment to the layout by referencing the fragment's class name with the name attribute. For example, we reference the book list fragment's class, BookListFragment, as follows:

We want our activity user interface to appear the same using fragments as it did before we converted it to use fragments. To do this, we add the same layout_width, layout_height, and layout_weight attribute values to the fragment elements as were on the ScrollView elements in the original layout.

With that, the complete layout resource file for the activity, activity_main.xml, now looks like the following code:

When the application is run, the user interface will now appear exactly as it did when it was defined entirely within the activity. If we're targeting Android devices running API Level 11 (Android 3.0) or later, there is no need to make any changes to the Activity class because the Activity class is simply loading and displaying the layout resource at this point.