Optimizing for Screen Sizes on Android

In previous posts we outlined the key guidelines for designing phone and tablet apps. Then we followed up with some secret tips for making them shine! Of course, bringing these apps to life is easier said than done so today we’ll explore the technical adventures in developing for the myriad screens on Android.

Resource Folders Make Your Life Easier

By far, the easiest way to ensure your app looks the way you intended is to use Resource Folders (1.6+). With Honeycomb 3.2, the ability to distinguish between screen sizes becomes more granular, granting the developer greater control. However, as of this post’s writing Ice Cream Sandwich is not out yet, so we’ll discuss the pre-Honeycomb version of resource folders.

Screen are split up into four categories: small, normal, large, and xlarge. These correspond to general form factors like phone, small tablets, and tablets. Each classification has a screen size range detailed below:

 

 

Resource files (xml files that describe things like layouts, dimensions, styles, etc.) are placed in folders in your Android project under the res directory. You can add modifiers to the name of a resource folder which  declare under what circumstances its files should be used.

For example, if you normally put your awsome_layout.xml file in layout, you can also place a version designed for large screens in a folder called layout-large. Thus, when the app runs on a tablet-sized device, the app will automatically use the awsome_layout.xml file found in ‘layout-large’ through no additional effort. Magic! We don’t go into the details of naming your folders, but a handy guide can be found here.

Be careful though, if your layouts are drastically different you must be certain you don’t refer to views in your code that only exist in one layout file without checking its existence. This can be prevented with thorough testing and good software design.

Detecting Screen Size In Code

You may also want to exhibit different behavior on larger screens in addition to having a separate layout. For tablets, one can allot more space for buttons on the Action Bar; on phones it is preferable to keep the layout uncluttered. An example taken from Pulse is in the tablet’s landscape mode. Clicking on a story causes the article to slide in from the right rather than completely covering the screen. This takes advantage of the extra real estate to browse stories while reading an article. To do this we need a way to tell if the device is xlarge, large, or normal in the code.

There is a class called DisplayMetrics that can give us some basic information about the device we’re running on. While this may seems like a great place to start, it could also lead to many layout bugs. Don’t simply use the screen width in pixels as a measure of device size; advances in screen density tosses this assumption out the window. A 4” phone can have a screen that is 540 pixels across, whereas a 7” tablet’s screen width is a mere 60 pixels wider at 600px. If you’re not careful you could end up with behavior intended for tablets on a phone, which would be wonky to say the least.

Instead, the screen size a particular device is using (equivalent to which modifier on a resource folder gets chosen) can be found in the Configuration class by using Resources.getConfiguration method. This is the same Configuration you use to see if the device is in landscape or portrait. Using the configuration object, you can retrieve the screenLayout field and see if the device is equal to the relevant constants. With this knowledge, your app can decide how to behave properly.

But what about dynamic values?

Using resources is a very painless way to incorporate device-dependent dimensions, but sometimes you want the layout to be more adaptable. For example, in Pulse there are horizontally scrolling tiles with each square taking up 1/3 of the screen width; even when the app is in landscape, the tile widths are the same.

Since we can’t possibly know what the screen width of the device is beforehand, we use a helper class to store these predicated constants. Our class follows the singleton pattern and is used whenever this parameter is needed. The parameters are initialized with the class and are available whenever they’re needed. Here is a super simple example of such a class:

/**
 * Sample class from Pulse
 *
 * Class to store and provide useful dimensions
 */
public class DimensionCalculator {

  private static DimensionCalculator mInstance = null;
  private int mScreenWidth;
  private int mTileWidth;

  /**
   * This class is a singleton
   */
  public static DimensionCalculator getInstance() {
    return SingletonHolder.instance;
  }

  /**
   * We use the SingletonHolder solution which is widely considered to be the
   * standard implementation in Java. Thanks to Fredia from the comments!
   */
  private static class SingletonHolder {
    public static final DimensionCalculator instance = new DimensionCalculator();
  }

  public class DimensionCalculator() {
     DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();
     mScreenWidth = Math.min(dm.widthPixels, dm.heightPixels);

     int numTiles = 3;
     int tileGap = 2;
     mTileWidth = (int) ((mScreenWidth - 4 * tileGap) / numTiles);
  }

  /**
   * Return the appropriate tile size for this device
   */
  public int getTileWidth() {
    return mTileWidth;
  }
}

 

Now that you have the tools to help create specialized layouts and designs for phones and tablets, you have absolutely no excuse for creating a tablet app that is just a blown up version of the phone app! Happy coding!

Best Practices for Releasing Android App Updates

Since the Android version of Pulse was released in July of last year, we’ve released 40 some updates. Here are some of the basic and more advanced best practices that we’ve accumulated:

The Basics

To publish an update, you must sign it with the same private key you used when you initially published the app. After you publish the initial version, back up your key and make sure you don’t forget the keystore password, otherwise your app will be un-updateable.  You’ll have to change the package name and publish as a separate app, but if you have any users, this is really a bad situation to be in. Make sure to back up your keystore!

In general, you want to test the new version on a variety of devices with different screen sizes and running different versions of Android. Building a community of beta-testers can be a great way to get some feedback and testing on a bunch of devices.

Testing Upgrades

Make sure to test various upgrade combinations. For example if you’re going to be releasing version 2.0, test upgrades from the last few versions (1.7 → 2.0, 1.8 → 2.0, 1.9 → 2.0) because a lot of users don’t install updates immediately after they are published.  To facilitate upgrade testing, you can keep an internal-only Dropbox folder with all the previous Market versions, and upload a new one when you have an updated version of the app.

When you have a previous version running on your device, add a few widgets (if your app has them) and shortcuts to the home screen, and make sure they stick around and keep working after upgrades. Double-check that you are not changing any of the things that cannot change. Also make sure user settings are persisted through upgrades.

Another thing to watch out for when upgrading is database changes. If you use the standard SQLite database, make sure your onUpdate method handles upgrading your database appropriately.  If you have any tutorials when the app first opens, check that the messaging is appropriate for upgrades as well as new installs. Someone who has been using your app for a year shouldn’t get a welcome message or another walkthrough of how your app works.

By accessing the version name (use PackageManager to get the the app’s PackageInfo which includes version name) and saving the current version name somewhere like SharedPrefs, you can easily figure out whether it’s a new install or upgrade or same version, and handle messaging or anything else appropriately.

Communication with Users

The changelog is the main place where you can communicate with users about changes in the recent version. Even if it’s a small update with trivial bug fixes, you definitely need to put something in the changelog. If you forget to add messaging in the changelog, many Android users will give you 1-star reviews if you have an empty changelog, and then after you do update the changelog, Android Market can take up to a few hours to persist those changes.

We tend to keep change history around for the last few updates, since not all users are on the current version.

Post-Publication

Even if you’ve done your best right up until you click “Publish,” bugs will probably show up in production. For the next few days, keep an eye on crash reports through the Android Developer dashboard, and be ready to release a quick fix release if anything urgent comes up.  For whichever version control system you use, always keep a branch synced to the latest published version, so if there are any crash reports or email feedback that require quick fixes, you won’t be introducing any new bugs into the app.

If you’ve found any other useful guidelines or tips for releasing updates, we’d love to hear them!

5 Tips for Honeycomb Design

The explosion of Android-powered devices has been a boon and a burden to Android app makers. An application’s potential reach has never been so vast, yet the diversity of hardware can throw designers/developers for a loop. Today, we’ll talk about how to design an application that takes advantage of all the slick new Honeycomb tablets (present and future) so your app can shine in all sizes!

 

1. Modular components make full use of screen real estate

Compared to phones, the amount of space available on tablets can be confounding – how on earth can you fill up all those pixels?! One thing you absolutely should not do is blindly scale up a design meant for a phone. It’s hard to envision something looking sillier than a 10” list view.

Please don't do this

Luckily there are several ways to tackle this issue, one of them is the concept of Fragments introduced in Honeycomb. Fragments are UI components that are meant to be modular and dynamic. The clearest example of this is in the official GMail app. On the phone, the user can view a list of all emails, which takes up the entire screen. Selecting an email will proceed to another screen showing the email conversation itself.

With the fragments, these list and email can appear side-by-side, allowing a user can view an email while gaining the additional context of their entire inbox.

 

2. Use dialogs

Sometimes it simply doesn’t make sense for a view to take up an entire screen. For situations like these, dialogs are quite handy. In Pulse, we enable users to connect with social networks when they wish to share stories – all of which require a login screen. On the phones, login utilizes the full window, but on a tablet such a view would be visually offensive. Dialogs provide an easy way to show these smaller components in a more natural manner.

 

3. Utilize 9 patches creatively

Unpredictable screen dimensions create some interesting challenges for the visual designer. Being pixel-perfect is possible, but not without some ingenuity. 9-patch image files allow you to determine how images get stretched when resized, which happens quite often when using relative layouts.

In the tutorial for saving stories with Pulse.Me, we use an arrow that points from the star button to the “.me” button. With the magic of 9-patches this arrow behaves correctly on all screen sizes and orientations.

 

4. Design for both landscape and portrait

Never assume a user will only use the app in one orientation. People have wildly different (and very strong) opinions on proper tablet usage and your app must accommodate both. In landscape mode, the wide screen makes horizontal layouts more natural. Avoid stretching things out to span the entire width unless absolutely necessary, otherwise you’ll most likely end up with a lot of blank space.

 

5. Remember the Honeycomb UI conventions

There are several UI patterns for Android tablet apps that users have come to expect from all applications. The first is the lack of a menu button. While users may have overlooked menu buttons on phones, they’re completely MIA in Honeycomb. In their place, the concept of an Action Bar governs what the user expects to be possible on a particular screen.

Even if you don’t explicitly use the Action Bar classes in the Android SDK, rolling your own is a good idea to fit the expectations of Honeycomb users. In general, the top left is reserved for going back to a previous screen, with the rest of the bar containing other actions. By following this pattern, a user will never feel lost or confused when using your application.

Now that you’re newly equipped with these tips, start creating compelling, beautiful apps!