I’ve been recently using the Android Compatibility Library in a project where I  found myself in the need for a tabbed layout, in which each of the tab’s children would be a fragment.  The usual way to do this is for the activity you need the tabs in to extend TabActivity. However, if you want to use fragments in your activity, you must extend android.support.v4.app.FragmentActivity instead. Since Java doesn’t allow multiple class inheritance, you find yourself in a bit of dilemma.

Well, there’s a simple solution for making this work. What you need is basically to use a TabHost to hold both the TabWidget (which are the actual tab labels) and their content. Since the TabHost is nothing more than a View, you can place that anywhere, including in the root of a fragment defined in XML, like the one below (tabs_fragment.xml). I am hoping the comments in the code are self-explanatory enough.

<?xml version="1.0" encoding="utf-8"?>
<TabHost
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@android:id/tabhost"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:background="#EFEFEF">

	<LinearLayout
		android:orientation="vertical"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent">

		<TabWidget
			android:id="@android:id/tabs"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content" />

			<FrameLayout
				android:id="@android:id/tabcontent"
				android:layout_width="fill_parent"
				android:layout_height="fill_parent">

				<FrameLayout
					android:id="@+id/tab_1"
					android:layout_width="fill_parent"
					android:layout_height="fill_parent" />

				<FrameLayout
					android:id="@+id/tab_2"
					android:layout_width="fill_parent"
					android:layout_height="fill_parent" />

			</FrameLayout>
	</LinearLayout>
</TabHost>

Don’t forget to use the correct ids (@android:id/tabhost, @android:id/tabs and @android:id/tabcontent) because the TabHost expects exactly these in order to retrieve the references for each component of the view. If you want to use a custom style for your tabs, it is easy to do so by creating a tab_selector.xml in /res/drawables in which you can use different colors or backgrounds for each tab state. You can find an example in the attached zip.

The equivalent code for actually creating the tabs is in TabsFragment.java below.

package org.codeandmagic.android;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;

public class TabsFragment extends Fragment implements OnTabChangeListener {

	private static final String TAG = "FragmentTabs";
	public static final String TAB_WORDS = "words";
	public static final String TAB_NUMBERS = "numbers";

	private View mRoot;
	private TabHost mTabHost;
	private int mCurrentTab;

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

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		mRoot = inflater.inflate(R.layout.tabs_fragment, null);
		mTabHost = (TabHost) mRoot.findViewById(android.R.id.tabhost);
		setupTabs();
		return mRoot;
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		setRetainInstance(true);

		mTabHost.setOnTabChangedListener(this);
		mTabHost.setCurrentTab(mCurrentTab);
		// manually start loading stuff in the first tab
		updateTab(TAB_WORDS, R.id.tab_1);
	}

	private void setupTabs() {
		mTabHost.setup(); // you must call this before adding your tabs!
		mTabHost.addTab(newTab(TAB_WORDS, R.string.tab_words, R.id.tab_1));
		mTabHost.addTab(newTab(TAB_NUMBERS, R.string.tab_numbers, R.id.tab_2));
	}

	private TabSpec newTab(String tag, int labelId, int tabContentId) {
		Log.d(TAG, "buildTab(): tag=" + tag);

		View indicator = LayoutInflater.from(getActivity()).inflate(
				R.layout.tab,
				(ViewGroup) mRoot.findViewById(android.R.id.tabs), false);
		((TextView) indicator.findViewById(R.id.text)).setText(labelId);

		TabSpec tabSpec = mTabHost.newTabSpec(tag);
		tabSpec.setIndicator(indicator);
		tabSpec.setContent(tabContentId);
		return tabSpec;
	}

	@Override
	public void onTabChanged(String tabId) {
		Log.d(TAG, "onTabChanged(): tabId=" + tabId);
		if (TAB_WORDS.equals(tabId)) {
			updateTab(tabId, R.id.tab_1);
			mCurrentTab = 0;
			return;
		}
		if (TAB_NUMBERS.equals(tabId)) {
			updateTab(tabId, R.id.tab_2);
			mCurrentTab = 1;
			return;
		}
	}

	private void updateTab(String tabId, int placeholder) {
		FragmentManager fm = getFragmentManager();
		if (fm.findFragmentByTag(tabId) == null) {
			fm.beginTransaction()
					.replace(placeholder, new MyListFragment(tabId), tabId)
					.commit();
		}
	}

}

Now that you have the content of your fragment in place, you need to create the tab’s children (which are fragments, like the title says). You can subclass the  android.support.v4.app.Fragment (I used a ListFragment in this example). You can also use loaders if your fragment needs to do some background work. In the example below, I use an AsyncTaskLoader to simulate a time-consuming operation in background, at the end of which some words or numbers are added into the list’s adapter and displayed inside a tab. You also need a LoaderCallback object (or your fragment can directly implement this interface) in order to communicate with the LoaderManager to create your loader and also know when its job is done.

package org.codeandmagic.android;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class MyListFragment extends ListFragment implements
		LoaderCallbacks<Void> {

	private static final String TAG = "FragmentTabs";

	private String mTag;
	private MyAdapter mAdapter;
	private ArrayList<String> mItems;
	private LayoutInflater mInflater;
	private int mTotal;
	private int mPosition;

	private static final String[] WORDS = { "Lorem", "ipsum", "dolor", "sit",
			"amet", "consectetur", "adipiscing", "elit", "Fusce", "pharetra",
			"luctus", "sodales" };
	private static final String[] NUMBERS = { "I", "II", "III", "IV", "V",
			"VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV" };

	private static final int SLEEP = 1000;

	private final int wordBarColor = R.color.word_bar;
	private final int numberBarColor = R.color.number_bar;

	public MyListFragment() {
	}

	public MyListFragment(String tag) {
		mTag = tag;
		mTotal = TabsFragment.TAB_WORDS.equals(mTag) ? WORDS.length
				: NUMBERS.length;

		Log.d(TAG, "Constructor: tag=" + tag);
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		// this is really important in order to save the state across screen
		// configuration changes for example
		setRetainInstance(true);

		mInflater = LayoutInflater.from(getActivity());

		// you only need to instantiate these the first time your fragment is
		// created; then, the method above will do the rest
		if (mAdapter == null) {
			mItems = new ArrayList<String>();
			mAdapter = new MyAdapter(getActivity(), mItems);
		}
		getListView().setAdapter(mAdapter);

		// initiate the loader to do the background work
		getLoaderManager().initLoader(0, null, this);
	}

	@Override
	public Loader<Void> onCreateLoader(int id, Bundle args) {
		AsyncTaskLoader<Void> loader = new AsyncTaskLoader<Void>(getActivity()) {

			@Override
			public Void loadInBackground() {
				try {
					// simulate some time consuming operation going on in the
					// background
					Thread.sleep(SLEEP);
				} catch (InterruptedException e) {
				}
				return null;
			}
		};
		// somehow the AsyncTaskLoader doesn't want to start its job without
		// calling this method
		loader.forceLoad();
		return loader;
	}

	@Override
	public void onLoadFinished(Loader<Void> loader, Void result) {

		// add the new item and let the adapter know in order to refresh the
		// views
		mItems.add(TabsFragment.TAB_WORDS.equals(mTag) ? WORDS[mPosition]
				: NUMBERS[mPosition]);
		mAdapter.notifyDataSetChanged();

		// advance in your list with one step
		mPosition++;
		if (mPosition < mTotal - 1) {
			getLoaderManager().restartLoader(0, null, this);
			Log.d(TAG, "onLoadFinished(): loading next...");
		} else {
			Log.d(TAG, "onLoadFinished(): done loading!");
		}
	}

	@Override
	public void onLoaderReset(Loader<Void> loader) {
	}

	private class MyAdapter extends ArrayAdapter<String> {

		public MyAdapter(Context context, List<String> objects) {
			super(context, R.layout.list_item, R.id.text, objects);
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View view = convertView;
			Wrapper wrapper;

			if (view == null) {
				view = mInflater.inflate(R.layout.list_item, null);
				wrapper = new Wrapper(view);
				view.setTag(wrapper);
			} else {
				wrapper = (Wrapper) view.getTag();
			}

			wrapper.getTextView().setText(getItem(position));
			wrapper.getBar().setBackgroundColor(
					mTag == TabsFragment.TAB_WORDS ? getResources().getColor(
							wordBarColor) : getResources().getColor(
							numberBarColor));
			return view;
		}

	}

	// use an wrapper (or view holder) object to limit calling the
	// findViewById() method, which parses the entire structure of your
	// XML in search for the ID of your view
	private class Wrapper {
		private final View mRoot;
		private TextView mText;
		private View mBar;

		public Wrapper(View root) {
			mRoot = root;
		}

		public TextView getTextView() {
			if (mText == null) {
				mText = (TextView) mRoot.findViewById(R.id.text);
			}
			return mText;
		}

		public View getBar() {
			if (mBar == null) {
				mBar = mRoot.findViewById(R.id.bar);
			}
			return mBar;
		}
	}
}

Now, all you need to do is create a FragmentActivity to accommodate your tabs fragment. You can either instantiate the fragment programmatically using the methods in the FragmentManager or by putting it directly in XML, like below (main.xml).

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">

	<fragment
		class="org.codeandmagic.android.TabsFragment"
		android:id="@+id/tabs_fragment"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent" />
</LinearLayout>

With that, your job is pretty much done. You can see  in the screenshot the result and also can download the project that’s available in the attached zip. Enjoy! :)

FragmentTabs.tar