This chapter introduces fragments, UI modularization, and the role fragments play in developing a modularized UI. The chapter demonstrates creating simple fragments and using fragments statically within activities.
Let us have a look at the topics to be covered:
The need for UI modularization
Fragments are the foundation of modularization
Support for fragments across Android versions
Creating fragments
By the end of this chapter, we will be able to create and use fragments within a static activity layout.
Chances are that the first class you learned to use when you became an Android developer was the Activity
class. After all, the Activity
class provided your app with a user interface. By organizing your user interface components onto an activity, the activity became the canvas on which you were painting your application masterpiece.
In the early days of Android, building an application's user interface directly within an activity worked reasonably well. The majority of early applications had a relatively simple user interface and the number of different Android device form factors was small. In most cases, with the help of a few layout resources, a single activity worked fine across different device form factors.
Today, Android devices come in a wide variety of form factors with incredible variation in their size and shape. Combine this with the rich, highly interactive user interfaces of modern Android applications, and the creation of a single activity that effectively manages the user interface across such divergent form factors becomes extremely difficult.
A possible solution is to define one activity to provide the user experience for a subset of device form factors; for example, smartphones. Then define another activity for a different subset of form factors such as tablets. The problem with this approach is that activities tend to have a lot of responsibilities beyond simply rendering the user interface. With multiple activities performing essentially the same tasks, we must either duplicate the logic within each of the activities, or increase the complexity of our program by finding ways to share the logic across the activities. The approach of using different activities for different form factors also substantially increases the number of activities in the program, easily doubling or tripling the number of activities required.
We need a better solution. We need a solution that allows us to modularize our application user interface into sections that we can arrange as needed within an activity. Fragments are that solution.
Android fragments allow us to partition the user interface into functional groupings of user interface components and logic. An activity can load and arrange the fragments as needed for a given device form factor. The fragments take care of the form factor details while the activity manages the overall user interface issues.
The Fragment
class was added to Android at API Level 11 (Android 3.0). This was the first version of Android that officially supported tablets. The addition of tablet support exacerbated an already difficult problem; developing Android applications was becoming increasingly difficult because of the wide variety of Android device form factors.
Fortunately, fragments provide a solution to the problem. With fragments, we can much more easily create applications that support a variety of form factors, because we can partition our user interfaces into effective groupings of components and their associated logic.
There was one problem with fragments. Up until very recently, the majority of Android devices had an API Level below 11 and therefore didn't support fragments. Fortunately, Google released the Android Support Library, available at http://developer.android.com/tools/extras/support-library.html, which makes fragments available to any device running API Level 4 (Android 1.6) or above. With the Android Support Library, fragments are now available to virtually every Android device in use.
Note
Applications created with Android Studio automatically include the Android Support Library, and therefore support fragments on virtually all SDK versions in use. If you will be using a development tool other than Android Studio to create applications that target devices running on a SDK level below 11, see the Android Developers Blog post, Fragments For All, available at http://android-developers.blogspot.com/2011/03/fragments-for-all.html, for directions on manually adding the Android Support Library to your projects.
Fragments not only simplify the way we create our application user interfaces but they also simplify many of the built-in Android user interface tasks. User interface concepts such as tabbed displays, list displays, and dialog boxes have all historically had distinctly different approaches. When we think about it, though, they are all variations on a common concept, that is, combining user interface components and logic into a functional group. Fragments formalize this concept, and therefore allow us to take a consistent approach to these formerly disparate tasks. We talk about each of these issues in detail as well as some of the specialized fragment classes such as the DialogFragment
class and the ListFragment
class later in this book.
Fragments do not replace activities but rather supplement them. A fragment always exists within an activity. An activity instance can contain any number of fragments but a given fragment instance can only exist within a single activity. A fragment is closely tied to the activity on which it exists and the lifetime of that fragment is tightly coupled to the lifetime of the containing activity. We'll talk much more about the close relationship between the lifetime of a fragment and the containing activity in Chapter 3, Fragment Lifecycle and Specialization.
One thing we don't want to do is make the common mistake of overusing fragments. So often when someone learns about fragments, they make the assumption that every activity must contain fragments, and that's simply not the case.
As we go through this book, we'll discuss the features and capabilities of fragments and a variety of scenarios where they work well. We'll always want to keep those in mind as we're building our applications. In those situations where fragments add value, we definitely want to use them. However, it is equally important that we avoid complicating our applications by using fragments in those cases where they do not provide value.
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 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:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- List of Book Titles --> <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:id="@+id/scrollTitles" android:layout_weight="1"> <RadioGroup android:id="@+id/bookSelectGroup" android:layout_height="wrap_content" android:layout_width="wrap_content" > <RadioButton android:id="@+id/dynamicUiBook" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/dynamicUiTitle" android:checked="true" /> <RadioButton android:id="@+id/android4NewBook" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/android4NewTitle" /> <!-- Other RadioButtons elided for clarify --> </RadioGroup> </ScrollView> <!-- Description of selected book --> <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:id="@+id/scrollDescription" android:layout_weight="1"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="@string/dynamicUiDescription" android:id="@+id/textView" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:gravity="fill_horizontal"/> </ScrollView> </LinearLayout>
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
This layout resource is reasonably simple and is explained as follows:
The overall layout is defined within a vertically-oriented
LinearLayout
element containing the twoScrollView
elementsBoth of the
ScrollView
elements have alayout_weight
value of1
that causes the top-levelLinearLayout
element to divide the screen equally between the twoScrollView
elementsThe top
ScrollView
element, with theid
value ofscrollTitles
, wraps aRadioGroup
element containing a series of theRadioButton
elements, one for each bookThe bottom
ScrollView
element, with theid
value ofscrollDescription
, contains aTextView
element that displays the selected book's description
The application's activity class, MainActivity
, inherits directly from the android.app.Activity
class. To display the activity's user interface, we override the onCreate
method and call the setContentView
method passing the R.layout.activity_main
layout resource ID.
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // load the activity_main layout resource setContentView(R.layout.activity_main); } // Other methods elided for clarity }
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:
<!-- List of Book Titles --> <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:id="@+id/scrollTitles" android:layout_weight="1"> <RadioGroup android:id="@+id/bookSelectGroup " android:layout_height="wrap_content" android:layout_width="wrap_content" > <RadioButton android:id="@+id/dynamicUiBook" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/dynamicUiTitle" android:checked="true" /> <RadioButton android:id="@+id/android4NewBook" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/android4NewTitle" /> <!-- Other RadioButtons elided for clarify --> </RadioGroup> </ScrollView>
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:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/scrollTitles"> <!—RadioGroup and RadioButton elements elided for clarity --> </ScrollView>
With the updated ScrollView
element, the fragment layout can now adapt to almost any layout it's referenced within.
For the book description, we'll define a layout resource file called fragment_book_desc.xml
. The fragment layout includes the contents of the activity layout resource's bottom ScrollView
element (has an id
value of scrollDescription
). Just as in the book list fragment, we'll remove the layout_weight
attribute, set the layout_height
attribute to match_parent
, and add the android
namespace prefix declaration.
The fragment_book_desc.xml
layout resource file appears as follows:
<!-- Description of selected book --> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/scrollDescription"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="@string/dynamicUiDescription" android:id="@+id/textView" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:gravity="fill_horizontal"/> </ScrollView>
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.
Note
For projects that rely on the Android Support Library to provide fragment support for pre-API Level 11 (Android 3.0) devices, use the android.support.v4.app.Fragment
class in place of the android.app.Fragment
class.
We'll call the class for the fragment that manages the book list, BookListFragment
. The class will directly extend the Fragment
class as follows:
Import android.app.Ftragment; public class BookListFragment extends Fragment { … }
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:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View viewHierarchy = inflater.inflate(R.layout.fragment_book_list, container, false); return viewHierarchy; }
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.
For the book description fragment, we'll define a class called BookDescFragment
. This class is identical to the BookListFragment
class except the BookDescFragment
class uses the R.layout.fragment_book_desc
layout resource as follows:
public class BookDescFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View viewHierarchy = inflater.inflate(R.layout.fragment_book_desc, container, false); return viewHierarchy; } }
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:
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <!-- List of Book Titles --> <!-- Description of selected book --> </LinearLayout>
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:
<fragment android:name="com.jwhh.fragments.BookListFragment" android:id="@+id/fragmentTitles"/>
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:
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <!-- List of Book Titles --> <fragment android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:name="com.jwhh.fragments.BookListFragment" android:id="@+id/fragmentTitles"/> <!-- Description of selected book --> <fragment android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:name="com.jwhh.fragments.BookDescFragment" android:id="@+id/fragmentDescription"/> </LinearLayout>
Note
If you are working with Android Studio, you might find a tools:layout
attribute on the fragment
element. This attribute is used by Android Studio to provide a preview of the layout within the graphical designer. It has no effect on your application's appearance when the application is run.
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.
When using the Android Support Library to provide pre-API Level 11 (Android 3.0) fragment support, we have one additional step. In this case, we have to make one small, but important change to our activity. We must change the MainActivity
class' base class from the Activity
class to the android.support.v4.app.FragmentActivity
class. Because the pre-API Level 11 Activity
class doesn't understand fragments, we use the FragmentActivity
class from the Android Support Library to add fragment support to our MainActivity
class.
The shift from the old thinking of being activity-oriented to the new thinking of being fragment-oriented opens our applications up to rich possibilities. Fragments allow us to better organize both the appearance of the user interface and the code we use to manage it. With fragments, our application user interface has a more modular approach that frees us from being tied to the specific capabilities of a small set of devices and prepares us to work with the rich devices of today, and the wide variety of new devices to come tomorrow.
In the next chapter, we'll build on the modularized user interface we've created with fragments to enable our application to automatically adapt to differences in the various device form factors with only minimal changes to our application.