Advertisement
Android SDK

Android SDK: Using Fragments

by

The notion of fragments was introduced in Android 3.0, API Level 11. Since then, the Android community has seen six major platform releases. Still, more than half of the active Android devices don't have this feature available to them natively. Developers must instead use the Android Support library if they are to use fragments with these devices. Learn how to create a basic fragment-based app using the Android Support library in this tutorial.

Step 0: Getting Started

This tutorial is for the Android developer who needs to target the vast majority of Android devices currently in users’ hands and wants to leverage the Fragment API, which simplifies support for a variety of screen sizes. You should be comfortable with Java, Eclipse, and be familiar with Android basics.

This tutorial is also for those who need more detailed instructions than what our other fragment tutorial gave or those who need a fresh take on fragments.

In order to fully see the effects and differences of a responsive, well-designed fragment screen design, you'll need two devices (or configured emulators): one with a regular phone-size screen and another with a tablet-sized screen.

Note that this code is based off the best-practices code created by the New Android Application wizard within Eclipse. The project can be downloaded and browsed at Google code hosting, as can the installable binary.

Step 1: Classic Two-Pane View

The example here will be the classic fragment example, also known as a “master-detail” flow. We start with a list of “things” (items, they could be songs, movies, pets, whatever) and, when a “thing” is clicked on, we display the details for that specific “thing”. If the device’s screen size can accommodate showing both the list and the details, two fragments will be used. Otherwise, just one is used and the user must click to drill down to the details.

Here is a figure showing the typical screen organization before fragments, or when the screen isn't large enough to handle so much information side-by-side:

Single Pane Fragments

And here is the screen design when done with fragments as it would display on a large or wide screened device like a tablet:

Dual Pane Fragments

Fragments don't remove the need for activities. And, in fact, you'll still need two activities: one activity will handle the case where both fragments are displayed (two pane mode) and another to handle the detail view when only the list was displayed on the first screen because the device screen size was small. Through the use of appropriately organized resources, the first activity will adjust dynamically in response to the screen width. Internally, this activity must know if it's in two pane mode or not so it can correctly place the detail fragment or spawn the detail activity as necessary, which will contain just the detail fragment.

To simplify: You will need to implement two Activity classes and two Fragment classes to handle these cases. The Activity classes are fairly simple, since they just display the correct fragment or fragments and control the app flow. For our example here we use the following Activity classes: the main class, CircleListActivity, the sometimes used detail class, CircleDetailActivity. We also have the two Fragment classes that actually perform layout and drawing: CircleListFragment and CircleDetailFragment. Yes, we're talking about circles. Circles are just things. No need to be concerned with the “thing” we've chosen.

Step 2: Creating the Primary Activity

The primary activity, CircleListActivity, is the starting point for loading either one or both fragments for the screen.

 
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class CircleListActivity extends FragmentActivity {
	private boolean mTwoPane;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_circle_list);

		if (findViewById(R.id.circle_detail_container) != null) {

			mTwoPane = true;

			((CircleListFragment) getSupportFragmentManager().findFragmentById(
					R.id.circle_list)).setActivateOnItemClick(true);
		}

	}

}

Note that we use the Android Support FragmentManager, not the one introduced in Android 3.0.

Yet, the Activity class does very little. All the main logic happens automatically because the Android operating system will load the correct layout resource version for the device when the setContentView() method call is made. This means you just need to focus your attention on the resources in question. How does the resource correctly choose to use one fragment or two?

Step 3: Defining the Layouts for the Screens

Pop Quiz: How many layouts do we need to display both fragments?

Answer: 3, but that doesn't include the layout of each fragment.

We need one layout that will display both the list fragment and the detail fragment. We need another layout that will just show the list fragment. Finally, we need a third layout that will show just the detail fragment.

Layouts can use a fragment tag to indicate a specific fragment to load. However, this only works for fixed fragments. For dynamic fragments, such as the detail fragment that will display different information based on what was chosen, we use a container view to indicate where the fragment should go in the layout. The most common container to use is a FrameLayout, as it only contains one child element and can then be positioned as a block anywhere in the containing layout.

To start, we'll create the two single fragment layouts.

First, the layout that will display just the list fragment:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/circle_list"
    android:name="com.mamlambo.circlesfragmentexample.CircleListFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
/>

Now the layout that displays just the details fragment:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/circle_detail_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
 />

If these look pretty simple, it's because they are. To reiterate, these are the layouts that dictate where the fragments will be, not that the fragments look like.

Finally, let's combine them into a layout that will display both fragments:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
    android:orientation="horizontal"
    android:showDividers="middle"
    >

    <fragment
        android:id="@+id/circle_list"
        android:name="com.mamlambo.circlesfragmentexample.CircleListFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/circle_detail_container"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3" />

</LinearLayout>

Now that you know what the layouts look like, you still might be wondering how the correct one gets chosen programmatically.

Step 4: Organizing the Layouts for Different Scenarios

You know that the Android operating system will choose appropriate resources based on current device characteristics. So, at first glance, you may start placing layouts in folders like layout-land, layout-large, layout-small, and so on. You'd name the single list fragment layout the same as the dual fragment layout and put them both in a variety of different folders. This might even work, but this method will be hard to maintain, with several different files for several identical layouts.

Instead, you can use references to refer to a particular item in Android from a particular resource folder. It's best to see an example of this. We'll name our three Activity-based layouts (as opposed to the individual fragments contained within): activity_circle_detail, activity_circle_list, and activity_circle_twopane. Then we'll put all three in the standard layout folder. You've already seen in the code, from Step 2 that we only ever load the activity_circle_list layout. Or do we?

Now let's add some references. A good way to determine if the screen is suitable for two side-by-side panes is by the width, in dp units. Let's say our dual-pane layout requires at least 850dp units of screen width. Let's create a folder called values-w850dp. That should cover high density 7" tablets and medium density 10" tablets in landscape mode, but it won't include xhdpi phones and will generally exclude portrait mode on most devices, except for some with very high resolution screens. In this folder, create a file called refs.xml and place the following inside it:

<resources>
    <item name="activity_circle_list" type="layout">@layout/activity_circle_twopane</item>
</resources>

How does this work? When the width is 850dp or greater, this reference will be used to load the layout. That means the dual-pane layout will be used instead of the individual panes being displayed separately. As this resource qualifier (folder naming convention) wasn't introduced until API Level 13, we'd also want to include this same reference in another suitable folder qualifier that has been around a lot longer, such as values-xlarge-land. Extra large is defined as at least 960dp by 720dp, so we also need to make sure it would only be shown in landscape mode by including the land qualifier.

With the layouts organized, it's time to turn our focus back to the Java code. While the right layout will draw on the screen when the list activity is first launched, what happens when one of the list items is tapped?

Step 5: Defining the List Fragment

The list fragment handles displaying our array of things (circles, in this case). It's not unlike a ListActivity in that it provides some handling of routine list tasks to simplify your own coding. The list needs to contain all of the circles from the data source (a mock data source, in this case), and it also needs to handle taps on specific items to show the details. However, since a fragment isn't an activity, it should pass up events that might potentially change the screen to the controlling Activity class. A tap that occurs in dual pane mode will load a fragment in the other pane on the same screen whereas a tap in single pane mode should load a new activity with the detail fragment. However, the fragment doesn't, and shouldn't, know about these configuration details.

You must, however, create a consistent way to pass this information to the containing or controlling activity, which does, and should, know about these details. Most commonly, this communication is managed through an interface class defined in the fragment that must be implemented by any activity that uses it -- a typical method of doing callbacks.

Here is the entire code for the CircleListFragment class, including a safe callback mechanism and the tap handling that calls the callback method to pass the workflow handling up to the controlling Activity.

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.mamlambo.circlesfragmentexample.dummy.DummyContent;

public class CircleListFragment extends ListFragment {

	private static final String STATE_ACTIVATED_POSITION = "activated_position";
	private Callbacks mCallbacks = sDummyCallbacks;
	private int mActivatedPosition = ListView.INVALID_POSITION;
	public interface Callbacks {
		public void onItemSelected(String id);
	}

	private static Callbacks sDummyCallbacks = new Callbacks() {
		@Override
		public void onItemSelected(String id) {
		}
	};

	public CircleListFragment() {
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(getActivity(),
				android.R.layout.simple_list_item_activated_1,
				android.R.id.text1, DummyContent.ITEMS));
	}

	@Override
	public void onViewCreated(View view, Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);

		if (savedInstanceState != null
				&amp;&amp; savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
			setActivatedPosition(savedInstanceState
					.getInt(STATE_ACTIVATED_POSITION));
		}
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);

		if (!(activity instanceof Callbacks)) {
			throw new IllegalStateException(
					"Activity must implement fragment's callbacks.");
		}

		mCallbacks = (Callbacks) activity;
	}

	@Override
	public void onDetach() {
		super.onDetach();

		mCallbacks = sDummyCallbacks;
	}

	@Override
	public void onListItemClick(ListView listView, View view, int position,
			long id) {
		super.onListItemClick(listView, view, position, id);

		mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
	}

	@Override
	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		if (mActivatedPosition != ListView.INVALID_POSITION) {
			outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
		}
	}

	public void setActivateOnItemClick(boolean activateOnItemClick) {
		getListView().setChoiceMode(
				activateOnItemClick ? ListView.CHOICE_MODE_SINGLE
						: ListView.CHOICE_MODE_NONE);
	}

	private void setActivatedPosition(int position) {
		if (position == ListView.INVALID_POSITION) {
			getListView().setItemChecked(mActivatedPosition, false);
		} else {
			getListView().setItemChecked(position, true);
		}

		mActivatedPosition = position;
	}
}

Other than the previously described functionality, there is some ListView management so that the tapped on list item shows as selected when in dual-pane mode and some basic fragment lifecycle management. As before, we are using the Support Fragment version so that we are backwards compatible with older devices. Make sure to import the correct package in the support library (import android.support.v4.app.ListFragment).

Now we need to return to the controlling Activity class so that it candle handle the callback.

Step 6: Updating the Primary Activity

Add the callback support to the primary Activity class, CircleListActivity, by having it implement the CircleListFragment.Callbacks interface created earlier. Then implement the onItemSelected() method to branch app behavior based on whether the device can handle two pane mode or not. Here is the fully updated class:

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class CircleListActivity extends FragmentActivity implements
		CircleListFragment.Callbacks {

	private boolean mTwoPane;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_circle_list);

		if (findViewById(R.id.circle_detail_container) != null) {
			mTwoPane = true;

			((CircleListFragment) getSupportFragmentManager().findFragmentById(
					R.id.circle_list)).setActivateOnItemClick(true);
		}

		// TODO: If exposing deep links into your app, handle intents here.
	}

	@Override
	public void onItemSelected(String id) {
		if (mTwoPane) {
			Bundle arguments = new Bundle();
			arguments.putString(CircleDetailFragment.ARG_ITEM_ID, id);
			CircleDetailFragment fragment = new CircleDetailFragment();
			fragment.setArguments(arguments);
			getSupportFragmentManager().beginTransaction()
					.replace(R.id.circle_detail_container, fragment).commit();

		} else {
			Intent detailIntent = new Intent(this, CircleDetailActivity.class);
			detailIntent.putExtra(CircleDetailFragment.ARG_ITEM_ID, id);
			startActivity(detailIntent);
		}
	}
}

Using the flag to determine if the dual-pane view is showing, the onItemSelected() method either starts a new Activity or replaces any current Fragment found in R.id.circle_detail_container with a new instance of the CircleDetailFragment class. For this sort of design, little more is needed. The identifier of which circle the user tapped on is either passed to the new Fragment instance via arguments or to the new Activity where it will turn the identifier into arguments, as well.

At this point, we need to implement both the CircleDetailActivity class for the single-pane case and implement the CircleDetailFragment class.

Step 7: Defining the Single Pane Detail Activity

Recall the simplicity of the layout for this Activity class. The layout is simply a placeholder for the detail Fragment class. All this Activity needs to do is get the identifier passed to it, turn that into a Fragment argument, then add the Fragment to the container.

package com.mamlambo.circlesfragmentexample;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class CircleDetailActivity extends FragmentActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_circle_detail);

				if (savedInstanceState == null) {
			Bundle arguments = new Bundle();
			arguments.putString(CircleDetailFragment.ARG_ITEM_ID, getIntent()
					.getStringExtra(CircleDetailFragment.ARG_ITEM_ID));
			CircleDetailFragment fragment = new CircleDetailFragment();
			fragment.setArguments(arguments);
			getSupportFragmentManager().beginTransaction()
					.add(R.id.circle_detail_container, fragment).commit();
		}
	}
}

If it sounded straightforward, you can now see that it is. The final piece we need to get to is the detail fragment itself.

Step 8: Show the Detail Fragment

The detail Fragment class needs to see what circle data should be loaded and displayed. Then, it needs to load and, well, display that data. The class will load a layout. The layout design can be generic, but should consider the fact that, as a fragment, it may or may not be full screen. In other words, as usual, don't make assumptions about the device screen size. Of course, also as usual, you can create multiple layouts based on resource qualifiers. Keep in mind these will be loaded based on overall device screen characteristics and not based on the display area characteristics (pane modes).

In this example, there's no further navigation, so no callback is needed as there was with the list fragment.

Without further ado, here's the entire code for the CircleDetailFragment class:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.mamlambo.circlesfragmentexample.dummy.DummyContent;

public class CircleDetailFragment extends Fragment {
	public static final String ARG_ITEM_ID = "item_id";

	private DummyContent.DummyItem mItem;
	public CircleDetailFragment() {
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if (getArguments().containsKey(ARG_ITEM_ID)) {
			mItem = DummyContent.ITEM_MAP.get(getArguments().getString(
					ARG_ITEM_ID));
		}
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View rootView = inflater.inflate(R.layout.fragment_circle_detail,
				container, false);

		if (mItem != null) {
			((TextView) rootView.findViewById(R.id.circle_detail))
					.setText(mItem.content);
			((ImageView) rootView.findViewById(R.id.circle_view))
					.setImageResource(mItem.resourceId);
		}

		return rootView;
	}
}

Although all of the code is basically the same as a non-Support fragment, note the proper import for android.support.v4.app.Fragment to be sure to use the right Android Support class.

Step 9: Updating the Android Manifest File for Fragments

Now we need to add the fragments to the manifest file, right? Actually, no. Unlike Activity classes, Fragments are not registered in the manifest file. So, simply add the activity classes to the manifest file and you're good to go.

Here's the sample manifest file for your viewing pleasure:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mamlambo.circlesfragmentexample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.mamlambo.circlesfragmentexample.CircleListActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.mamlambo.circlesfragmentexample.CircleDetailActivity"
            android:label="@string/title_circle_detail"
            android:parentActivityName=".CircleListActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".CircleListActivity" />
        </activity>
    </application>

</manifest>

Note the wide range of Android SDK versions that can be supported with this approach by using the Support library Fragment functionality.

Step 10: Running the App

While we have not presented the complete source code pasted within this tutorial and resources to the application, you can fill in those details yourself. Or you can download the code (see Step 0), compile and run it. Or you can download the pre-compiled binary and install it.

However you get to it, when running in a device configuration with 850 dp wide or the screen is extra large (at least 960x640 dp), you'll see this screen design (a 1920x1200 pixel screen at 10"):

And when the screen width is less than 850dp, the layout reverts to single pane mode, such as this 1280px wide phone display in landscape mode:

Or this 768 pixel wide portrait phone:

Or this 1200px wide portrait display on a tablet:

Due to the use of abstracted measurements, such as the use of dp units, you could see the two different layouts on a screen with identical pixel resolutions, yet different pixel densities. This helps keep the information density (more pixels doesn't always mean your users want more data) from being too high on physically small screens and is one of the great things about the Android resource system.

Conclusion

You've learned the basics of using the Android Fragment API available within the Android Support library. While there is much, much more to fragments, this tutorial gives you a starting point for a very common use-case that can dramatically improve the look and feel of many applications or portions of applications, regardless of what version of Android they are running with or screens they are being displayed upon.

About the Authors

Mobile developers Lauren Darcey and Shane Conder have coauthored several books on Android development: an in-depth programming book entitled Android Wireless Application Development (now in it's third edition as a two-volume set), Sams Teach Yourself Android Application Development in 24 Hours, and Learning Android Application Programming for the Kindle Fire: A Hands-On Guide to Building Your First Android Application. When not writing, they spend their time developing mobile software at their company and providing consulting services. They can be reached at via email to androidwirelessdev+mt@gmail.com, via their blog at androidbook.blogspot.com, and on Twitter @androidwireless.

Need More Help Writing Android Apps? Check out our Latest Books and Resources!

Buy Android Wireless Application Development, 3rd Edition, Volume 1  Buy Sam's Teach Yourself Android Application Development in 24 Hours, 2nd Edition  Mamlambo code at Code Canyon

Related Posts
  • Code
    Android SDK
    Create a Music Player on Android: Project Setup0d63m preview image@2x
    The Android platform provides resources for handling media playback, which your apps can use to create an interface between the user and their music files. In this tutorial series, we will create a basic music player application for Android. The app will present a list of songs on the user device, so that the user can select songs to play. The app will also present controls for interacting with playback and will continue playing when the user moves away from the app, with a notification displayed while playback elapses.Read More…
  • Code
    Android SDK
    Using the Accelerometer on Android06lom preview image@2x
    In this tutorial, we are going to explore how to use the accelerometer, one of the many hardware sensors of modern smartphones, in an Android application. I'll explain what an accelerometer is and why it may be something you want to take advantage of in your Android applications.Read More…
  • Code
    Android SDK
    Create a Hangman Game: Project SetupAndroid hangman game preview retina
    In this series, I will show you how to create a simple Hangman game for Android. In every tutorial, you'll learn a number of fundamental skills for creating Android applications. The application will consist of two screens, include user interaction involving adapters, contain dialogs and an action bar, and leverage XML for data storage. Let's get started.Read More…
  • Code
    Android SDK
    Consuming Web Services with kSOAPEd4e2 preview image@2x
    In this tutorial, you'll learn how to consume web services using the popular kSOAP library in an Android application. kSOAP is a reputable library loved by developers who have the need to parse WSDL (Web Service Definition Language) and SOAP (Simple Object Access Protocol) messages.Read More…
  • Code
    Android SDK
    Android SDK: Detecting GesturesAndroid preview@2x
    The Android SDK supports a variety of gestures you can detect, allowing you to tailor the ways in which users interact with your applications. In this tutorial, we will look at the framework that allows developers to add gesture detection to a simple application, working through the process of supporting the fling gesture and outlining how to support other gestures. Once you've completed the development for a basic fling function, you'll be able to add additional gestures to the application yourself.Read More…
  • Code
    Android SDK
    Android SDK: Create an Arithmetic Game - Setup and Interface CreationMath game preview@2x
    The Android platform provides the ability to facilitate interaction, to carry out computation, and to save data. To explore some of these basic features with a practical focus, this series will create a simple arithmetic game for the Android platform. The series will involve creating user interface elements and layouts, four Activity classes, and one helper class. The gameplay logic will be relatively complex if you do not have much Java experience, but you will learn to use various Java control structures along the way! Read More…