Making your app tablet-friendly in 3 steps
The Android tablets are here, and so are the non-tablet-optimised applications. To ensure that tablet users have a good experience using your app, a few optimisations are necessary to make it feel native. The three most important of these are described in this article, all with backward compatibility using the official Android compatibility library.
This article will focus on optimising for the the Android 3.0+ Honeycomb tablets, even though there are a some tablets out there with a firmware version below 3.0. I expect you to have some basic knowledge about Android development.
Throughout this article we will develop a tablet friendly recipe application with backward-compatibility and phone support.
The information given about the individual new apis are only introductions. For more advanced and detailed information I refer to the official Android Developer's Guide.
Preparation
To be able to follow along this guide you will need the most recent Android SDK, which you can download from the Android Developer website.
If you want to follow along with your own code, create a new project and give it a name (i.e. "Spicy Dishes"). You can browse or download the source code on GitHub.
The Android Compatibility Library
To make backward compatibility easy, there is a compatibility library included in the SDK. Locate the library in <sdk-path>/extras/android/compatibility/ and add it to your project. You can read more about the compatibility library here.
1) The "Holographic" Theme
In Android 3.0 Honeycomb, a new UI-theme has been introduced. If you have not yet seen the new theme you should take a look at the Android 3.0 platform highligts.
To apply the theme to your application, you will need to set the SDK target version to 11, which is the version code of Android 3.0. Put the following tag in your androidmanifest.xml file inside the Application element:
<uses-sdk android:targetSdkVersion="11" />
Your application will still look the same when running on any older Android version. If you have enabled themes for individual activities, these will override the holographic theme. To avoid this problem, read how to select a theme based on platform version.
2) Fragments
The probably most significant addition to the Android 3.0 framework is the Fragment api. Think of the a fragment as a sub-activity or viewgroup, which most likely will contain a group of views. The main purpose of the fragment api is to make it easier to reuse parts of the user interface, on both phones and tablets. To give an example of the purpose of fragments, take a look at the illustration below.

Source: http://developer.android.com/guide/topics/fundamentals/fragments.html
The illustration shows two phone activities on the left and a single tablet activity on the right. The two phone activities are combined in the tablet activity. Each activity on the phone shows a fragment, while the single activity on the tablet shows both fragments.
To demonstrate how to use fragments, let us add a list and a detail view based on the illustration above to our recipe project. The list will show the name of the different recipes and the detail view will show the actual recipe.
Start out by making two activities - one to contain the list and, for the tablet, the detail screen, and another activity to contain the detail screen for the phone. Be sure that you extend FragmentActivity ínstead of the default Activity, as this is the "updated" version from the compatibility library.
We give the first activity the name MainActivity and the second activity DetailActivity:
1 | public class MainActivity extends FragmentActivity |
1 | public class DetailActivity extends FragmentActivity |
We will leave these activities for a few minutes. First it is time to create the fragments.
Most of your fragments will probably extend Fragment, which is the most basic fragment and can contain any subview. The Fragment class have four subclasses, however, which you may also want use:
DialogFragmentListFragmentPreferenceFragmentWebViewFragment
In this example we will use the Fragment and the ListFragment classes.
Create two new classes, one called RecipeList and another one called RecipeDetailFragment. The RecipeList extends ListFragment, and the RecipeDetailFragment extends the regular Fragment class.
During the explanation different compile errors may arise, but do not worry - we will add the new files/ids later on.
Open the RecipeList class and add the following fields:
1 2 3 4 | public static final String[] RECIPE_NAMES = new String[] { "Turtle Soup", "Frikadeller (Danish Meatballs)", "Omelette", "Cucumber Soup", "Danish Pork Sausage", "Fried Bugs" }; private int mCurrentSelectedItemIndex = -1; private boolean mIsTablet = false; |
The first field is a String array which will hold the names of our recipes. The mCurrentSelectedItemIndex is the current selected item index in the list, and the mIsTablet is a boolean indicating whether the application is running on a tablet or not.
Now add the following method to the class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, RECIPE_NAMES)); // Read the selected list item after orientation changes and similar if (savedInstanceState != null) { mCurrentSelectedItemIndex = savedInstanceState.getInt("currentListIndex", -1); } // This is a tablet if this view exists View recipeDetails = getActivity().findViewById(R.id.recipe_details); mIsTablet = recipeDetails != null && recipeDetails.getVisibility() == View.VISIBLE; // If this is a tablet, an item can be selected, otherwise just clicked if(mIsTablet) { getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); } else { getListView().setChoiceMode(ListView.CHOICE_MODE_NONE); } // If this is shown on a tablet and a list item is selected, update the recipe detail view if(mIsTablet && mCurrentSelectedItemIndex != -1) { showRecipeDetails(); } } |
This will be called when the activity which owns this fragment is created. We initialise the ListFragment, and if you have worked with ListView's before, you may be able to recognise a lot of this code. What we do is first setting the list items using an ArrayAdapter. Then we check whether the screen has just been rotated - if so, we load the selected list item index. After that, we initialise the boolean mIsTablet by checking which layout file that has been set as the content view. This will be described more detailed later on. Then we set the choice mode of the list - either single choice or no choice. At last we update the detail fragment view if a list item is selected.
Now we are going to handle list selections. Add the following code to the fragment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | @Override public void onListItemClick(ListView l, View v, int position, long id) { mCurrentSelectedItemIndex = position; showRecipeDetails(); } private void showRecipeDetails() { if(mIsTablet) { // This is a tablet. Show the recipe in the detail fragment // Set the list item as checked getListView().setItemChecked(mCurrentSelectedItemIndex, true); // Get the fragment instance RecipeDetail details = (RecipeDetail) getFragmentManager().findFragmentById(R.id.recipe_details); // Is the current visible recipe the same as the clicked? If so, there is no need to update if (details == null || details.getRecipeIndex() != mCurrentSelectedItemIndex) { // Make new fragment instance to show the recipe details = RecipeDetail.newInstance(mCurrentSelectedItemIndex); // Replace the old fragment with the new one FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.recipe_details, details); // Use a fade animation. This makes it clear that this is not a new "layer" // above the current, but a replacement ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.commit(); } } else { // This is not a tablet - start a new activity Intent intent = new Intent(getActivity(), DetailActivity.class); // Send the recipe index to the new activity intent.putExtra(DetailActivity.EXTRA_RECIPE_INDEX, mCurrentSelectedItemIndex); startActivity(intent); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("currentListIndex", mCurrentSelectedItemIndex); } |
In this code, we react to a selection in the list. First we set the selected item index and then we show the tablet details. We create a new instance of the RecipeDetail-fragment and replaces it with the current fragment (if any). We use the FragmentManager to do this "fragment transaction". If the device is not a tablet, we simply start a new activity containing only the RecipeFragment.
The last method saves the current selected item on orientation changes and similar. That is all for the RecipeList-fragment!
Now we will move on to the RecipeDetail-fragment. This fragment is much simpler than the RecipeList, and it is very similar to a normal activity.
Start by adding the following lines to your code:
1 2 3 4 5 6 7 8 9 | private int mRecipeIndex; public static RecipeDetail newInstance(int recipeIndex) { // Create a new fragment instance RecipeDetail detail = new RecipeDetail(); // Set the recipe index detail.setRecipeIndex(recipeIndex); return detail; } |
What you can see is that we are using the factory method pattern. We do this to make it easy to pass the recipe index from the list to the detail view. The code is very simple - it creates a new instance of the fragment and sets the current recipe index.
Now we are going to set the actual layout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if(container == null) { // There is no need to create the fragment in this case. It will not be visible anyway. return null; } // Inflate the view from the xml layout resource View v = inflater.inflate(R.layout.recipe_details, container, false); // Set the different details ((TextView) v.findViewById(R.id.recipe_title)).setText(RecipeList.RECIPE_NAMES[mRecipeIndex]); // This should have been a real photo, but I don't have one so we use the icon ((ImageView) v.findViewById(R.id.recipe_photo)).setImageResource(R.drawable.icon); ((TextView) v.findViewById(R.id.recipe_description)).setText(String.format("This recipe has index #%1$d, and it is called '%2$s'.", mRecipeIndex, RecipeList.RECIPE_NAMES[mRecipeIndex])); return v; } |
The method is called when the fragment is about to be shown, and all you have to do is returning the View - you even get an instance of a LayoutInflator with you as an argument!
In the beginning we check if the container is null - if so, the fragment will not be shown, and there is no need to create it. After that, we inflate the layout file, which we will be creating in a second. We set the different layout properties and return the view instance. We just have to add a getter and a setter:
1 2 3 4 5 6 7 | public int getRecipeIndex() { return mRecipeIndex; } public void setRecipeIndex(int index) { mRecipeIndex = index; } |
The recipe_details.xml layout resource shows the recipe details. It should be very simple and familiar to you:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/recipe_title" android:textSize="40sp" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal"/> <ImageView android:id="@+id/recipe_photo" android:src="@drawable/icon" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal"/> <TextView android:id="@+id/recipe_description" android:textSize="20sp" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> |
That is it! Both fragments are done, and all we need now is the two activities. Let us start with the MainActivity, which shows the list on a phone, but also the detail view on a tablet. The code is very, very simple - the onCreate() is the only method needed:
1 2 3 4 5 | @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_recipe_activity); } |
What is more important here, however, is the layout file. To distinguish between the tablet version and the phone version, we will have to create two versions of the same layout file. We will call the layout file main_recipe_activity.xml, and place the phone layout file in the res/layout/[code] directory, and the tablet layout file in the [code]res/layout-xlarge/[code] directory. The [code]-xlarge-extension makes this file the layout file when the screen is "xtra large" (i.e. a tablet).
The layout code in the phone-layout file located in res/layout/main_recipe_activity.xml looks like this:
1 2 3 4 5 6 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="org.kaloersoftware.SpicyDishes.fragments.RecipeList" android:id="@+id/recipe_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> |
The <fragment /> tag has a reference to the RecipeList fragment class, which fills the whole screen.
The tablet-layout which is located in the res/layout-xlarge/main_recipe_activity.xml directory looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="org.kaloersoftware.SpicyDishes.fragments.RecipeList" android:id="@+id/recipe_list" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/recipe_details" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" android:background="?android:attr/detailsElementBackground" /> </LinearLayout> |
Notice that this activity contains the same
<fragment /> tag as the phone layout, but also a FrameLayout. The FrameLayout is used to take space for the detail fragment when no item is selected. When an item has been selected, the FrameLayout will be the container for it.In the
RecipeList fragment we set whether the device was a tablet or a phone by checking whether the FrameLayout existed.
All we need now is the DetailActivity, which shows the RecipeDetail fragment on phones. The class is very simple. Add the following code in the file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static final String EXTRA_RECIPE_INDEX = "recipeIndex"; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Get the recipe index int recipeIndex = getIntent().getIntExtra(EXTRA_RECIPE_INDEX, -1); // Show the new fragment (full screen) RecipeDetail details = RecipeDetail.newInstance(recipeIndex); getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit(); } |
All this does is reading the recipe index from the Intent and then shows the RecipeDetail fragment. Remember to add this class to your Android manifest file.
Now we have a perfectly optimised application which runs great on both phones with small screens and tablets with large screens. Give it a go on your device or on the emulator - but be aware that the Honeycomb emulator is awfully slow!

For more information about fragments, please take a look at the official documentation.
3) Action Bar
On Honeycomb tablets, a new Action Bar has been added in the top of an application. The purpose of the Action Bar is to make the options menu always accessible. The actions which, on phones, are visible after the menu button is pressed, will be put in the Action Bar on a Honeycomb device. The Action Bar can also contain other views, such as tabs, and it shows the name and icon of the application as default.
We will continue using the recipe application as an example, to which we will add an "add" and a "delete" function. However, these actions will not work, but only show how to use the Action Bar.
The first thing to do is to add the actual menu items. We will do this in xml, as it is the simplest and easiest way to set the different properties of the Action Bar.
Create a new folder in the res/ directory called "menu", and create a new file with the name "recipe_menu.xml". Most of the content in this file will not be described in this post, so you may want to check out the documentation about menu resources.
Add the code below to the file:
1 2 3 4 5 6 7 8 9 10 11 | <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_add" android:icon="@android:drawable/ic_menu_add" android:title="New Recipe" android:showAsAction="ifRoom|withText" /> <item android:id="@+id/menu_delete" android:icon="@android:drawable/ic_menu_delete" android:title="Delete" android:showAsAction="ifRoom|withText" /> </menu> |
The first item shows the "Add" button, and the second item shows the "Delete" button. The android:showAsAction="ifRoom|withText" attribute makes the item show with both its icon and the text, if there is space for it.
Now, open the RecipeDetail class and add this method:
1 2 3 4 5 | @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.recipe_menu, menu); super.onCreateOptionsMenu(menu, inflater); } |
The method will "connect" the options menu of the fragment to the xml resource. This will be called when the fragment is created on a tablet, or if this is on a phone, when the menu button is clicked.
Also, you will need to add the following line in your factory method, just before you return the instance:
1 | detail.setHasOptionsMenu(true); |
The last thing to do is to handle the clicks. Add this method to the RecipeDetail fragment class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.menu_add: // TODO: add recipe Toast.makeText(getActivity(), "Add recipe selected", Toast.LENGTH_LONG).show(); return true; case R.id.menu_delete: // TODO: delete recipe Toast.makeText(getActivity(), "Delete recipe selected", Toast.LENGTH_LONG).show(); return true; case android.R.id.home: // TODO: Handle app icon click Toast.makeText(getActivity(), "Home icon selected", Toast.LENGTH_LONG).show(); return true; default: return super.onOptionsItemSelected(item); } } |
That's it! Run the application and see how it works. You can optain an instance of the ActionBar class by calling getActivity().getActionBar(). The Action Bar is officially documented in Using the Action Bar and ActionBar.

This introduction has only covered the basics, and if you want to make your application stand out you may also want to look at custom Action Bars and the new animation framework in Honeycomb. However, I hope this has been useful and that you are ready to start optimising your applications for tablets!
Popular blog posts
-
Android Preferences
Preferences are an important part of an Android application. It is important to let the users have the choice to modify...
-
Incoming SMS Messages
During the development of version 1.3 of Kaloer Clock, I wanted to show an icon when a new sms message was received....
-
Permissions in Kaloer Clock
Android application security has recently become a focus of interest when talking about Android. A chinese developer...
-
Android Plurals
A useful, but sadly rarely used string resource is plurals. Plurals are used for words or phrases which are spelled...
Comments
This given code had helped me
Just wanted to say, thank you for taking tthe time to share this with us. I will share this info with as many developers as I can.
That insight's pfrecet for what I need. Thanks!
Thank you , nice tutorial and very well explained.
how to make each list item creates its own views in DetailActivity. for example
clicking on Turtle Soup should display recipe_details.xml, Frikadeller displays Frikadeller .xml and so on
please explain
Post new comment