Four Simple Rules to Avoid DisplayMetrics Antipatterns in Android Code

Effectively combine characteristics and qualifiers for optimum layouts

The DisplayMetrics Red Flag

A search of GitHub returns more than 42,000 hits for the class name DisplayMetrics. This is a red flag. Although there are safe and valuable uses for this information, a quick look at the code using this class reveals that most programs query it to determine screen dimensions, using code like this:

DisplayMetrics displaymetrics = new DisplayMetrics();
        ((WindowManager)context.getSystemService("window")).getDefaultDisplay().getMetrics(displaymetrics);
        int i = Math.max(displaymetrics.heightPixels, displaymetrics.widthPixels);
        sScreenNailSize = i / 2;
        sThumbNailSize = i / 5;

The programs then make decisions about how the program should present its user interface. This is dangerous, because it tempts the programmer to make decisions with awful long-range consequences, when these decisions should be left up to the Android run-time.

How to Break Lots of Apps in One Easy Step

How dangerous is it? Pull up a random app on your Android device, go to the Settings and select Font Size, then select Huge.

Now see how many apps break:

  • How many have fixed-size views in their layouts where text overflows its bounds?
  • How many “fixed” that bug by setting the font size, and ignoring your preferences?
  • How many make incorrect layout decisions where objects don’t quite fit?
  • How many lock the app’s UI to a landscape or portrait orientation?

When you make your own decisions, based on screen dimension and other parameters, about how to present the user interface, you enter a danger zone that spawns bugs that can easily escape detection in both automated and manual testing. If bugs are caught late in the game, they create pressure to implement lame fixes.

The Only Way To Win Is Not To Play the Game

Aren’t you forced to make decisions about presentation? The answer is “No.” You should not be asking “How high, how wide, how dense, what font,” etc.

You should let Android ask the questions and make decisions about presentation. The only question, then, is how many answers you need to provide. Using multiple layouts for different configurations, and avoiding fixed values in layouts, you can make a system of layouts and let Android choose which layouts to use for different screen sizes and orientations.

This is a table of characteristics and qualifiers:
http://developer.android.com/guide/practices/screens_support.html#qualifiers

Android has qualifiers for size, density, orientation, and aspect ratio. Holy combinatorial explosion, Batman! With 2 to 5 qualifiers for each independent characteristic, there are dozens of combinations. But, for most programs, a couple of independent qualifiers for a couple characteristics will do.

Simple Combinations Cover Many Configurations

The simplest way to cover nearly every possible configuration is to use a combination of layouts and resources, and avoid layout specifications that can look bad with very large or very small fonts:

  • Provide layouts for normal screen sizes and large or xlarge screen sizes, and, optionally, layouts for port and land orientation.
  • Provide drawable resources, usually images, for all four primary densities: ldpi, mdpi, hdpi, and xhdpi.
  • Do not use fixed heights in layouts, and minimize use of fixed widths.

That requires you to define one to four variants of each layout, and four variants of each image. Not too onerous. Once you have tested your UI on a variety of sizes and densities, you can tell whether you need more layout variants under more qualifiers.

Fragment and Layout

One more pattern will eliminate the need for code that asks about your screen dimensions: drive your Fragment use from layouts. That is, do not make decisions about how many Fragment objects fit on your screen in your code. Instead, let the layout files control Fragment instantiation. Then, the only decision you need to make is whether the Fragment is already on the screen, or whether you need to start a new Activity to display the desired Fragment.

The following code snippet shows the logic for passing a Bundle to a Fragment, or to a new Activity:

public void onListItemClick(ListView l, View v, int position, long id) {
	Cursor c = ((CursorAdapter)getListView().getAdapter()).getCursor();
	String item = buildItemInfo(c, position);
	String tableInfo = buildDatabaseInfo(c);
	Bundle data = ((MainActivity) getActivity()).buildDataBundle(item,
				tableInfo);
	int n = getActionBar().getTabCount();

	if (0 != n) {
		doLoad(n, data);
	} else {
		startActivity(new Intent(this,
TabActivity.class).putExtras(data));
		}
	}
}

This code infers the presence of a Fragment from the presence of a Tab object in the ActionBar. Linking Tab objects and Fragment objects is a common pattern. The program then decides whether or not it needs to start a new Activity that contains the Fragment object. That’s the only logic in this program that takes a different action based on screen size—and it never asks about screen size directly. Instead, the Fragment exists, or not, based on whether a layout for a large or small screen was chosen.

No Code for Size Decisions

Following the patterns I’ve suggested, you can eliminate code that makes decisions based on size, and tune your application using Android’s declarative UI capabilities instead of getting on a treadmill of tweaking program logic every time you encounter an unexpected hardware configuration.

To sum up:

  1. Don’t use fixed-size layout dimensions.
  2. Do use multiple layout variations for different qualifiers.
  3. Do provide multiple images for different densities.
  4. Let layouts drive your Fragment object creation.
tags: , , ,