19 September 2011

Preparing for Handsets

[This post is by Scott Main, lead tech writer for developer.android.com. — Tim Bray]

Early this year, Honeycomb (Android 3.0) launched for tablets. Although Honeycomb remains tablets-only, the upcoming Ice Cream Sandwich (ICS) release will support big screens, small screens, and everything in between. This is the way Android will stay from now on: the same version runs on all screen sizes.

Some Honeycomb apps assume that they’ll run only on a large screen, and have baked that into their designs. This assumption is currently true, but will become false with the arrival of ICS, because Android apps are forward-compatible — an app developed for Honeycomb is compatible with a device running ICS, which could be a tablet, a phone, or something else.


If you’ve developed a tablet app on Honeycomb, it’s important that your app do one of two things: prevent installation on smaller screens or (preferably) support smaller screens with the same APK.

Making your Honeycomb app for tablets only

If you don’t want your app to be used on handsets (perhaps it truly makes sense only on a large screen) or you need more time to update it, add the following <supports-screens> declaration to your manifest:

<manifest ... >
    <supports-screens android:smallScreens="false"
                      android:normalScreens="false"
                      android:largeScreens="false"
                      android:xlargeScreens="true"
                      android:requiresSmallestWidthDp="600" />
    <application ... >
        ...
    </application>
</manifest>

This describes your app’s screen-size support in two different ways:

  • It declares that the app does not support the screen size buckets “small”, “normal”, and “large”, which are traditionally not tablets

  • It declares that the app requires a screen size with a minimum usable area that is at least 600dp wide

The first technique is for devices that are running Android 3.1 or older, because those devices declare their size based on generalized screen size buckets. The requiresSmallestWidthDp attribute is for devices running Android 3.2 and newer, which added the capability for apps to specify their size requirements based on a minimum number of density-independent pixels. In this example, the app declares a minimum width requirement of 600dp, which generally implies a 7”-or-greater screen.

Your size choice might be different, of course, based on how well your design works on different screen sizes; for example, if your design works well only on screens that are 9” or larger, you might require a minimum width of 720dp.

The catch is that you must compile your application against Android 3.2 or higher in order to use the requiresSmallestWidthDp attribute. Older versions don’t understand this attribute and will raise a compile-time error. The safest thing to do is develop your app against the platform that matches the API level you’ve set for minSdkVersion. When you’re making final preparations to build your release candidate, change the build target to Android 3.2 and add the requiresSmallestWidthDp attribute. Android versions older than 3.2 simply ignore that XML attribute, so there’s no risk of a runtime failure.

For more information about why the “smallest width” screen size is important for supporting different screen sizes, read New Tools for Managing Screen Sizes (really; it’s got lots of things you need to know).

Making your Honeycomb app work on handsets

On the other hand, if you want to distribute your app to devices of all sizes, we recommend that you update your existing Honeycomb app to work on smaller screens as well, rather than publishing multiple APKs.

Optimizing for handsets can be tricky if your designs currently use all of a large screen to deliver content. It’s worth the effort, though, because Ice Cream Sandwich brings the Honeycomb APIs to handsets and you’ll significantly increase the user-base for your app. Using a single APK for all devices also simplifies your updating and publishing process and makes it easier for users to identify your app.

Here are two guidelines to help make your Honeycomb tablet app work well on handsets:

  • Build your design around Fragments that you can reuse in different combinations, in single-pane layouts on handsets and multi-pane layouts on tablets

  • Be conservative with your Action Bar design so the system can adjust its layout based on the screen size

Creating single-pane and multi-pane layouts

The most effective way to optimize your app for both handsets and tablets is to combine fragments in different ways to create “single-pane” layouts for handsets and “multi-pane” layouts for tablets. There are two approaches to doing this:

  • For any screen in which your tablet version displays multiple fragments, use the same activity for handsets, but show only one fragment at a time — swapping the fragments within the activity when necessary.

  • Use separate activities to host each fragment on a handset. For example, when the tablet UI uses two fragments in an activity, use the same activity for handsets, but supply an alternative layout that includes just one fragment. When you need to switch fragments (such as when the user selects an item), start another activity that hosts the other fragment.

The approach you choose depends on your app design and personal preferences. The first option (single activity) requires that you dynamically add each fragment to the activity at runtime---rather than declare the fragments in your activity’s layout file — because you cannot remove a fragment from an activity if it’s been declared in the XML layout. You might also need to update the action bar each time the fragments change, depending on what actions or navigation modes are provided for the fragment. In some cases, these factors might not matter to your app, so using one activity and swapping fragments will work well. Other times, however, using just one activity and dynamically swapping fragments can make your code more complicated, because you must manage all the fragment combinations in the activity’s code rather than leveraging alternative layout files.

I’m going to talk about the second option in more detail. It might be a little more up-front work, because each fragment must work well across separate activities, but it usually pays off. It means that you can use alternative layout files that define different fragment combinations, keep fragment code modular, simplify action bar management, and let the system handle all the back stack work.

The following figure demonstrates how an application with two fragments can be arranged for both handsets and tablets when using separate activities for the handset design:

In this app, Activity A is the “main activity” and uses different layouts to display either one or two fragments at a time, depending on the size of the screen. When on a handset-sized screen, the layout contains only Fragment A (the list view); when on a tablet-sized screen, the layout contains both Fragment A and Fragment B.

Here’s res/layout/main.xml for handsets:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment class="com.example.android.TitlesFragment"
              android:id="@+id/list_frag"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
</FrameLayout>

And res/layout-large/main.xml for tablets:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/frags">
  <fragment class="com.example.android.TitlesFragment"
            android:id="@+id/list_frag"
            android:layout_width="@dimen/titles_size"
            android:layout_height="match_parent"/>
  <fragment class="com.example.android.DetailsFragment"
            android:id="@+id/details_frag"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
</LinearLayout>

How the application responds when a user selects an item from the list depends on whether Fragment B is available in the layout. If Fragment B is there, Activity A notifies Fragment B to update itself. If Fragment B is not in the layout, Activity A starts Activity B (which hosts Fragment B).

To implement this pattern for your application, it's important that you develop your fragments to be highly compartmentalized. Specifically, you should follow two general guidelines:

  • Do not manipulate one fragment directly from another.

  • Keep all code that concerns content in a fragment inside that fragment, rather than putting it in the host activity’s code.

To avoid directly calling one fragment from another, declare a callback interface in each fragment class that it can use to deliver events to its host activity, which implements the callback interface. When the activity receives a callback due to an event (such as the user selecting a list item), it acts appropriately based on the current fragment configuration.

For example, Activity A from above handles item selections like this:

/** This is a callback that the list fragment (Fragment A) calls
    when a list item is selected */
public void onItemSelected(int position) {
  DisplayFragment fragB = (DisplayFragment) getFragmentManager()
                              .findFragmentById(R.id.display_frag);
  if (fragB == null) {
      // DisplayFragment (Fragment B) is not in the layout, 
      // start DisplayActivity (Activity B)
      // and pass it the info about the selected item
      Intent intent = new Intent(this, DisplayActivity.class);
      intent.putExtra("position", position);
      startActivity(intent);
  } else {
      // DisplayFragment (Fragment B) is in the layout, tell it to update
      fragB.updateContent(position);
  }
}

When DisplayActivity (Activity B) starts, it reads the data delivered by the Intent and passes it to the DisplayFragment (Fragment B).

If Fragment B needs to deliver a result back to Fragment A, then the process works similarly with a callback interface between Fragment B and Activity B. That is, Activity B implements a callback interface defined by Fragment B. When Activity B gets the callback, it sets the result for the activity and finishes itself. Activity A then receives the result and delivers it to Fragment A.

For a complete demonstration of this technique for creating different fragment combinations for different tablets and handsets, look at the code for this updated version of the Honeycomb Gallery sample.

Making the Action Bar work on handsets

As long as you’ve been using the framework’s implementation of ActionBar for your tablet app (rather than building your own), the conversion from tablets to handsets should be painless. The Android system will do the work for you; all you need to do is ensure that your action bar design is flexible. Here are some important tips:

  • When setting a menu item to be an action item, avoid using the “always” value. Use “ifRoom” for action items you’d like to add to the action bar. Now, you might need “always” when an action view does not have an alternative action for the overflow menu or when a menu item added by a fragment is low in the menu order and it must jump into the action bar at all times. But you should not use “always” more than once or twice.

  • When possible, provide icons for all action items and declare showAsAction="ifRoom|withText". This way, if there’s not enough room for the text, but there is enough for the icon, then just the icon may be used.

  • Avoid using custom navigation modes in the action bar. Use the built-in tab and drop-down navigation modes — they’re designed to be flexible and adapt to different screen sizes. For example, when the width is too narrow for both tabs and other action items, the tabs appear below the action bar. If your app requires a custom navigation mode in the action bar, thoroughly test it on smaller screens when Ice Cream Sandwich becomes available and make any adjustments necessary for a narrow action bar.

For example, the mock ups below demonstrates how the system might adapt an app’s action bar based on the available screen space. On the handset, only two action items fit, so the remaining menu items appear in the traditional menu and the tabs appear in a separate row. On the tablet, more action items can fit in the action bar and so do the tabs.

Some other tips

  • When working with a ListView, consider how you might provide more or less information in each list item based on the available space. That is, you can create alternative layouts to be used by the items in your list adapter such that a large screen might display more detail for each item.

  • Create alternative resource files for values such as integers, dimensions, and even booleans. Using size qualifiers for these resources, you can easily apply different layout sizes, font sizes, or enable/disable features based on the current screen size.

Testing your handset support

At this point you might be wondering, “How do I test my layout for smaller screens without a handset that runs Honeycomb?” Well, until Ice Cream Sandwich is available for the SDK, you technically can’t. So don’t publish your changes until you’re able to test on a device or emulator running ICS.

However, you can begin some early testing of your alternative layouts with a little trick: instead of using the “large” configuration qualifier for the tablet layouts, use the “land” qualifier (that is, instead of res/layout-large/main.xml, use res/layout-land/main.xml). This way, a Honeycomb tablet (or emulator) in landscape orientation uses your tablet design and the same device in portrait orientation uses your handset design. Just be certain to switch back to using the size qualifiers once you’re able to test on ICS.

Conclusion

Ice Cream Sandwich is coming, and with it, handsets will be able to install apps built on Honeycomb. We haven’t released the ICS SDK just yet, but you can start preparing your Honeycomb apps by thinking about how they should work on smaller screens.

So if you have a Honeycomb tablet app out there (and by that, I mean an app with minSdkVersion="11" or higher), you should make sure it’s available only on large screen devices for now. We hope that you’ll then follow our advice here and optimize your tablet app to support smaller screens, using the same APK for both tablets and handsets.

If your app supports API levels lower than 11, then there’s probably nothing you need to do right now, because your app is already running on handset devices. When the ICS SDK does arrive, though, it’ll still be important that you verify your app’s performance on the new platform.

Stay tuned to the blog for more information about ICS as it nears release.