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:

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:

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

Sign up for the O'Reilly Programming Newsletter to get weekly insight from industry insiders.
topic: Programming
  • Anthony

    I’m currently making my first app and this is useful, thanks. I have been trying to follow most of this already so far (the layout and such is much more time consuming than I envisaged), but I wanted to check something. When you say don’t use fixed size layout dimensions, does this mean specifying actual sizes (dp, px)? Like if I want a button to be say 32dp high, I really don’t have a choice but to do this I think? Or say padding for a certain amount. I do plan to have different dimens.xml files and layout files for a few screen sizes though with higher padding on tablet sizes and such, but yeah this is hard.

    • Zigurd Mednieks

      I really do mean “Don’t use fixed-size layout” if possible. Look at relative layouts, and keep your button sizes, for example, responsive to the text in the button, especially if you are translating your app into Chinese or Japanese. If you make the height of the button wrap the content and you make width relative to other parts of a relative layout, while using padding to specify space around text, you get buttons that don’t break when you add a strange new language or the app runs on a device with a strange combination of pixel density and dimensions.

      If you do that, you can probably do without multiple dimens.xml. While that’s better than ad-hoc fudges for dimensions embedded in code, a solution based on multiple sets of dimensions can almost always be avoided for layouts that consist of standard Android widgets.

      • Anthony

        Thank you again, I have now changed a few things around. I had some of the layout files reading text sizes from a dimensions file. I had been trying to make it easier if I need to adjust a text size for multiple elements (e.g button text) across my app, but I realise now this is just going to complicate things. All I have in there now is some dimensions for paint objects on a custom view which don’t always look right with percentage derived sizes.

        As far as my button height, I added minHeight (to 38) and then set wrap_content instead which I think is safer than specifying layout_height as 38. Getting there.

        • Zigurd Mednieks

          You’ve got it! Minimums, and, in much rarer cases, maximums are a much better way to size layout elements. You can’t account for every contingency, but you can make your layouts remarkably bullet-resistant.

          The dominance of handset form-factor and portrait mode is a bad influence that gets reinforced by iPhone port-itis. If you seek a few corner-cases and fix them, you will be much less subject to bad surprises after release.

  • rober34

    Unfortunately if you do advanced stuff like custom Views and ViewGroups, overriding onDraw or draw, you do need to use DisplayMetrics. The thing is, you can use it correctly. The problem you pinpoint about changing font size can be correctly addressed taking into account the field densityScaled of the DisplayMetrics object.