http://www.developer.com/ws/android/development-tools/create-flexible-android-uis-with-fragments.html

Back to article

Create Flexible Android UIs with Fragments


June 6, 2011

First introduced in Android 3.0 (Honeycomb), the Fragment API is now available on all current Android platform versions via a compatibility package provided by Google. This library makes using the new Fragment API an effective way to develop flexible user interfaces for Android. Read on to learn how to use Fragments in your applications, including those written to API Level 4 (Android 1.6) and up.

What Is an Android Fragment?

Before we begin, let's talk a bit about what fragments are. Obviously, they are a new type of Android Java object with the Fragment API. But what are they for?

Traditionally, Android applications have been made up of two basic components: a layout (the screen contents) and an associated Activity class (that comprises the functions and handles any data processing and lifecycle events for that screen). The Android fragment is a new concept that basically decouples, or loosens, the relationship between the layout visual details and the activity.

To us, a Fragment can be thought of much like a sub-activity. Fragments can do most of what an activity can do, but exist within an Activity instance. Much like an activity usually represents an entire screen, a Fragment represents a portion of a screen. Fragments can be referenced from within layouts to specify how to control different portions of a screen. When used with alternative layout techniques, fragments provide tremendous flexibility in screen design. The big advantage of a fragment (over, say, a FrameLayout object and using statements within a layout) is that not only does a Fragment handle its own lifecycle, it can exist within different activities, depending on the needs of the application.

Let's explore the ramifications of these statements. The classic Fragment example involves building an application with a screen with a top-level list and another accompanying details screen. Traditionally, these entities would be displayed on two separate screens with two different Activity classes--one screen with the ListView and another which displays the details for a chosen ListView item. Using fragments, you can make a ListView fragment and a details view fragment. Then, through simple layout changes, you can mix and match these fragments in different ways, depending on your needs and the device configuration. For example, on tablets with sufficiently large or wide screens, you might show the fragments side-by-side on the same screen, whereas on smaller screens of smartphones, you might show them on separate screens.

Sample Code

The sample code that accompanies this tutorial is available online in open source form. The code listings you find here are part of this sample project.

Adding the Compatibility Library to Your Android Project in Eclipse

We feel fragments are so useful and flexible that nearly every Android project can benefit from using them. Google seems to agree, since they've made the Android Compatibility package available through the Android SDK and AVD Manager, making the compatible Fragments API accessible to nearly all versions of Android.

The first thing you'll need to do is download the Android Compatibility package. After you've downloaded it, find the library by browsing to your Android SDK directory, and then finding the /extras/android/compatibility/v4 directory. Within this directory, you'll find a file called android-support-v4.jar. Copy this file to your Android project's /lib directory, usually created at the root of the project. (You may need to create the directory yourself if this is your first library.)

Adding the Compatibility Library to Your Android Project in Eclipse

Once you've done this, edit the Eclipse project settings of your project to reference this library: Under Project, Properties, Java Build Path, choose the Libraries tab, then press the Add JARs… button. Find the /lib directory and select the android-support-v4.jar file, shown below. Finally press OK to confirm.

Once you've registered the Android Compatibility package for use within your project, you can start to use fragments. For instance, to create a class extending FragmentActivity, you'd need to import the android.support.v4.app.FragmentActivity class. When you're building against a version of Android prior to 3.0, Eclipse's automatic import organization will now automatically find this class and reference it. If you're targeting Android 3.0 and above, you may need to explicitly use this import to make sure you're building using the specific FragmentActivity version included with the compatibility library.

Layouts with Android Fragments

A fragment can be included within a layout much like other layout objects. You use the tag, along with your typical properties for width, height, and a unique identifier to define a fragment within a layout resource file. The primary difference between a View control definition and a fragment definition is that the fragment's name property must reference the fragment class name that will be responsible for drawing in that area of the screen. For example, here is a layout containing two fragments:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  >
  <fragment
    android:name="com.mamlambo.tutorial.fragments.SampleListFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:id="@+id/image_list_fragment"
    android:layout_weight="25">
  </fragment>
  <fragment
    android:name="com.mamlambo.tutorial.fragments.SampleViewerFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:id="@+id/image_viewer_fragment"
    android:layout_weight="75">
  </fragment>
</LinearLayout>

Coding with Android Fragments

Although fragments simplify the coding of dynamic user interfaces, there is a small overhead cost to using them. Although your layouts will ultimately determine which fragments are displayed, the code will still need to make decisions to determine whether or not to update an existing fragment or launch a new activity, should that fragment not exist in the current activity context.

For instance, the following code uses the findFragmentById() method to determine if a fragment is available within a specific layout. If it is not defined, a new activity is launched to display that fragment.

@Override
public void onListItemSelected(int index) {
  SampleViewerFragment imageViewer = (SampleViewerFragment) getSupportFragmentManager()
      .findFragmentById(R.id.image_viewer_fragment);

if (imageViewer == null || ! imageViewer.isInLayout()) { Intent showImage = new Intent(getApplicationContext(), SampleViewerActivity.class); showImage.putExtra("index", index); startActivity(showImage); } else { imageViewer.update(index); } }

You also end up needing just as many activity classes as before, though not all will be used on every device. There is little reason not to design your applications with fragments, even if your current UI design or target devices don't require them at this time. In the future, or when supporting a wide variety of devices, most applications will likely find good use cases for fragments, although games using their own frameworks with all of the UI implemented through OpenGL are probably a reasonable exception.

Since user interaction now typically happens within the fragments themselves, you need to code up the logic to determine whether or not to launch an activity from with the fragment, itself, or from the parent activity. Either way, some communication is needed between the fragment and its activity. Any fragment can call the getActivity() method to determine the Activity class it exists within. From there, a call to the UI methods of the activity can enable the fragment to determine more about the screen and layout. Or, more simply, the fragment could just call a method within an Activity that does this checking on demand. We've gone this route by adding a callback via a listener object. The interface for the listener is defined in the custom fragment class and the method is implemented in the Activity class. Here's a complete implementation of a ListFragment class:

public class SampleListFragment extends ListFragment {
  private int index = 0;
  private ListItemSelectedListener selectedListener;

@Override public void onListItemClick(ListView l, View v, int position, long id) { index = position; selectedListener.onListItemSelected(position); }
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setListAdapter(ArrayAdapter.createFromResource(getActivity(), R.array.image_titles, android.R.layout.simple_list_item_1));
if (savedInstanceState != null) { index = savedInstanceState.getInt("index", 0); selectedListener.onListItemSelected(index); } }
@Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("index", index); }
@Override public void onAttach(Activity activity) { super.onAttach(activity); try { selectedListener = (ListItemSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement ListItemSelectedListener in Activity"); } }
public interface ListItemSelectedListener { public void onListItemSelected(int index); } }

As you can see, this approach is straightforward. An adapter is set in the onActivityCreated() method. When the fragment is attached to an activity, the listener is assigned and verified by using the Activity instance passed in to the onAttach() method. When the user clicks on an item in the ListView, the listener is triggered by calling the onListItemSelected() that we've implemented in the Activity class. The state is saved and restored.

An interesting aspect here is the use of the savedInstanceState in the onActivityCreated() method. On the surface, this looks to just save which item is being viewed and restore that. When the orientation changes, the instance data is saved, and this section of code is triggered. If the orientation switched from the two fragment view to the single fragment view, the activity remains the same, which is the activity that will show just the list. But once this code is triggered, the intent that ultimately launches the viewer activity is triggered. And so, when you rotate from landscape to portrait (in the example) the viewer activity is shown. Pressing the back key returns the user to the list. Let's talk about the viewer activity.

We launch the viewer activity when the viewer fragment is not defined in the current activity. This activity must also know about the viewer fragment and any configuration it might need. As you see in the above code listing, this information is sent to the other activity using a parameter passed with the intent extras. This information is then passed on to the fragment in just the same way. Here's the complete code for a viewer activity to display the fragment on its own screen:

public class SampleViewerActivity extends FragmentActivity {
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { finish(); return; }
setContentView(R.layout.image_viewer_activity); Intent launchingIntent = getIntent(); int index = launchingIntent.getIntExtra("index", 0); SampleViewerFragment viewer = (SampleViewerFragment) getSupportFragmentManager() .findFragmentById(R.id.image_viewer_fragment); viewer.update(index); } }

As you see, it's merely a way to show the viewer fragment. One interesting aspect is that if its onCreate() method is called when the device has switched back to landscape mode, it finishes, returning the user to the previous screen. When it is only used on landscape screens, this method works fine. Should you choose to use it in other screen orientation configurations, this must be updated to match. For the sample, this creates a smooth transition between orientations.

The viewer fragment is very simple. It inflates a layout resource and returns a View object as a result of the onCreateView() method being called. Then it has an implementation of the update() method you've seen called a couple of times. That's all there is to it. You can view the code in the online code repository.

Now, when the user rotates from portrait mode to landscape mode, the screen switches from the single fragment per screen (activity) mode to the dual fragment mode. You can add further alternative layouts to better control the look on different types of screens. For instance, maybe you only want the two-fragment layout when on large screen tablets. Instead of putting the alternative layout resource in the /layout-land directory, you could put it in the /layout-xlarge-land directory. This is a case where you'd need to update the viewer activity to not finish merely when the screen orientation changes.

Here we see the landscape view:

Android viewer fragment - landscape

If you then change the orientation to landscape, you'll see the same image:

Android viewer fragment - change orientation

And pressing the back button will return you to the list view:

Android viewer fragment - list view

Conclusion

The Fragment API, introduced in Android 3.0, is a great API for creating flexible and dynamic user interfaces. You've learned how quickly a dynamic user interface can be created by walking through most of the pieces of an entire application that looks and behaves differently depending on screen orientation and screen size. Since this functionality is provided through a compatibility library, any applications that target Android 1.6 and beyond can benefit from the improvements to user interface flexibility. This has been a brief introduction to the Fragment API, which has many more interesting features, such as transitions, the back stack, dialog fragments, and more. When targeting Android 3.0 and later, there are even more features not found in the compatibility library.

About the Authors

Shane Conder Shane Conder and Lauren Darcey--Contributing Editors, Mobile Development--have coauthored two books on Android development: an in-depth programming book entitled Android Wireless Application Development (ISBN-13: 978-0-321-62709-4) and Sams Teach Yourself Android Application Development in 24 Hours (ISBN-13: 978-0-321-67335-0). When not writing, they spend their time developing mobile software at their company and providing consulting services.

 

 

Lauren Darcey

Sitemap | Contact Us