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”.

<?xml version="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:

public class ContactViewer extends FragmentActivity {

private static final String FRAG_TAG

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

public void onCreate(Bundle state) {

super.onCreate(state);


setContentView(R.layout.main);

 

installFragment();

 

// ...

}


private void 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:

Landscape

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

Smooshed landscape

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:

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

>

<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.

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:

http://developer.android.com/guide/practices/screens_support.html

All of this was introduced back in the Eclair days.  In Part II, though, I have a neat trick that’s buried in the Honeycomb docs that makes this same code compatible with even older and smaller phones.

About these ads

About bmeike
Android developer and evangelist in Oakland, CA

2 Responses to Multi-platform fragments, Part I

  1. 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?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: