Multi-platform fragments, Part I

This is going to be a bit long. I’ve been away from Android for a while, mostly looking at embedded Linux and Meego… but that’s another story. I’ve been meaning to write about some clever tricks for building applications that can work on a wide variety of platforms. I’ll do it in two posts.

The advent of tablets/slates/pads, brings the problem of making an application’s UI look good on a wide variety of devices to a whole new level. Until Honeycomb, with device independent pixels and a couple of fine tuned bitmaps, you could probably cover most of the devices out there. Now that Fragments have arrived, an app may actually have substantially different behaviours on different platforms. On a small screen it may show only a single window at a time and use the back button for navigation. On a larger screen, however, it might work need multiple windows to make use of the space. Sounds like a nightmare…

It turns out that it isn’t so bad. A little experimentation and a close read of the docs reveal couple of cute tricks will contain the whole issue in just a few lines of code.

First off, use the ACL (Android Compatibility Library). An app that is based on Fragments, without the ACL cannot run on pre-Honeycomb Android. Game over. In order to support a wide variety of platforms, you’ve got to code to the ACL.

To use it, you just copy it from its home:

$ANDROID_SDK/extras/android/compatibility/v4/android-support-v4.jar

… to a directory named “lib” in your project 9and add it to your Eclipse build path).

Consider an example app that displays information about your Contacts. It uses fragments and is meant for a tablet in landscape orientation Here’s its layout, the file “main.xml” in the directory “res/layout”.

<?xmlversion="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"

>

<ListView

android:id="@+id/contacts"

android:layout_width="0dp"

android:layout_height="fill_parent"

android:layout_weight="1"

/>

<FrameLayout

android:id="@+id/contact_detail"

android:layout_width="0dp"

android:layout_height="fill_parent"

android:layout_weight="2"

android:background="@color/blue"

/>

</LinearLayout>

The FrameLayout will be replaced by a fragment, in the code. That looks like this:

publicclass ContactViewer extends FragmentActivity {

privatestaticfinal String FRAG_TAG

= ContactViewer.class.getCanonicalName() + ".fragment";

publicvoid onCreate(Bundle state) {

super.onCreate(state);

setContentView(R.layout.main);

installFragment();

// ...

}

privatevoid installFragment() {

FragmentManager fragMgr = getSupportFragmentManager();

if (null != fragMgr.findFragmentByTag(FRAG_TAG)) { return; }

FragmentTransaction xact = fragMgr.beginTransaction();

xact.add(

R.id.contact_detail,

ContactDetailFragment.newInstance(null, null),

FRAG_TAG);

xact.commit();

}

Pretty straighforward fragment code. ContactDetailFragment is the fragment class and R.id.contact_detail is where it goes, the FrameLayout. I’ve mentioned, previously, using fragment’s tagging facility to prevent leaking them.

If you run this on a tablet, landscape WXGA, it looks pretty good:

Running it on a phone, in portrait WVGA800, is another story:

The screen is acutually still big enough to support two windows but the proportions have to be different and they have to be layed out vertically. This turns out to be dead simple. In the “res” directory, create a new sub-directory named “layout-port”, next to the original “layout”. Copy “main.xml” into it and reorient it for the smaller portrait screen:

<?xmlversion="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"

>

<ListView

android:id="@+id/contacts"

android:layout_width="fill_parent"

android:layout_height="0dp"

android:layout_weight="2"

/>

<FrameLayout

android:id="@+id/contact_detail"

android:layout_width="fill_parent"

android:layout_height="0dp"

android:layout_weight="1"

android:background="@color/blue"

/>

</LinearLayout>

We might copy “contact_detail.xml” over as well and tweak font sizes and such a little so that everything looks nice. With no code changes the app UI will now look pretty good on a wide variety of screens — including the Honeycomb tablet rotated to portrait.

The Android system allows you to group resources according to the configuration of the runtime device screen. The documentation has the details of how this works. Basically, Android will prefer resources from a subdirectory of “res” the that most closely corresponds to the device on which your app is running. It is all covered here:

2 Responses to Multi-platform fragments, Part I

Write more, thats all I have to say. Literally, it seems as though you relied on the video to make your point.
You clearly know what youre talking about, why waste your intelligence on just posting videos
to your blog when you could be giving us something informative to read?