Stop ignoring Android Lint, use it

Android Tools Photo credit: Unknown via Visualhunt / CC BY.

The Android Lint utility is probably one of the most powerful tools in your arsenal that you are not using. I could come up with many reasons why developers don't seem to take full advantage of it, but likely one of the main problems is that it's... well, a bit noisy.

Here's what I mean: I started up a brand new Android project and before I ever wrote a single line of code, before I ever built any UI, there were two Lint warnings. Create an Activity, add a few views, and it's already starting to look like an insurmountable problem and something best left to its own.

I want to tell you how you can take control of this and turn it back into a useful tool.

Goal

To start with, let's set our goal. What we want to achieve is...

  • Run lint on every build
  • If there are any new lint issues, break the build
  • Get to zero unchecked lint errors and warnings

Now, let's work through the practicalities of making this happen.

Android Tools Photo credit: xkcd / CC BY 2.5.

Build

Christian has written previously about our Travis setup, so if you're looking to setup CI for your project, that's a great place to start. The build target already runs lint, but if you want to run it explicitly you can always do that by running ./gradlew lint. By default, lint will break the build on errors, but not on warnings, which is why warnings tend to go unnoticed until there's a build-up of hundreds of them. We need to fix this somehow because we want it to shout on every warning. There are two ways to approach it.

Exhibit A: The shock diet

It's in fact very easy to make lint treat warnings as breaking errors. All you need to do is add the following configuration to gradle:

    lintOptions {
        warningsAsErrors true
    }

And now your build will break on any lint warning found. Note: Of course some warnings should probably never get to break the build (e.g. a new version of a dependency being available), but we'll talk about how to deal with that in a minute.

Unfortunately, if you've been ignoring lint warnings for a while, it may be very difficult to get your project to build again after making this change, as you will have hundreds of issues to sort through. For that reason I would recommend that you go down this path only when starting a new project or if you have been keeping an eye on your lint warnings and know that you don't have too many to deal with. For all else, there is still hope. Enter...

Exhibit B: Baby steps

Unless you're willing to take some time and do nothing but sort out lint warnings (which your product owner/manager will probably not be thrilled by), you want a way to make this improvement gradually. To achieve this, our team has taken the reverse approach from the one above. We continue to allow warnings to pass the build, but one by one, we fix them and raise them to error status so they can't occur again.

This kind of configuration requires some fine-grained lint controls. You get that using the lint xml configuration file.

Configuration

Start in build.gradle by adding the following

    lintOptions {
        lintConfig file("lint.xml")
    }

Your lint.xml can look something like this to start with:

<?xml version="1.0" encoding="UTF-8"?>
<lint>
    <!-- Changes the severity of these to "error" for getting to a warning-free build -->
    <issue id="UnusedResources" severity="error"/>
</lint>

What we're doing here is taking UnusedResources, normally a warning, and raising it to error level. This means that from now on this will show up as an error in Lint reports, so even if we're allowing warnings to pass, any instance of this will cause the build to break.

One by one, you can set tasks for your team to fix these warnings and then add them here as errors so from that point on they are enforced. Once all the issues have been dealt with, you can cleanup this file and switch all warnings to errors as shown previously.

Suppressing

Now, before we let you get on with it, there is something that must be clarified. When we say dealing with lint issues, this doesn't mean that you have to fix every single thing that lint complains about.

  • Sometimes you just don't care about the issue.
  • Some warnings really should never break the build, so you want to keep them to a "quiet" level.
  • Sometimes lint is wrong, either because it inferred something incorrectly or because even though its "fears" are correct, you have a way of knowing they are unwarranted that is beyond lint's ability.

There is nothing wrong with suppressing lint in these situations and it's actually the only realistic way of getting to a #warningsfree scenario. Remember, this is a tool that is meant to help you ship better and quicker, not a roadblock to slow you down.

The docs on suppression give you all details you need on how to do this, so I'll run through it quickly.

  • Whenever possible try to suppress the issue on the smallest scale possible (Java statement or XML element).
  • If that's not possible, try and suppress it for the entire method and as a last resort, for the class (you should rarely need this).
  • Finally, the last resort is the lint.xml file itself, which applies changes module-wide. Let's spend a bit of time on this to show you how you can make finer adjustments in there.

We previously showed you how to turn UnusedResources into an error. That's a very good practice (and an easy place to start), but there are a few instances where this lint check gives us false positives, so in reality our configuration looks a bit more like this:

<?xml version="1.0" encoding="UTF-8"?>
<lint>
    <!-- Changes the severity of these to "error" for getting to a warning-free build -->
    <issue id="UnusedResources" severity="error">
        <!-- Ignore what google services generates -->
        <ignore path="**/google-services/**" />
        <!-- Ignore generated resValues -->
        <ignore path="build.gradle" />
        <!-- Not used by project, but used by AS editor -->
        <ignore path="src/main/res/layout/preview_theme_editor.xml" />
        <ignore path="src/main/res/values/strings_preview.xml" />
    </issue>
</lint>

What we've done here is raised UnusedResources to error as before, but in addition we're explicitly ignoring some file paths. Note the build.gradle line which is a way of saying "ignore resources that were generated from the build.gradle file" (rather than specifying the generated.xml file where they end up).

Another example comes from one of our projects where we have achieved zero warnings and are now treating all warnings as errors. We quickly found that a bit more tweaking is needed for sanity.

<?xml version="1.0" encoding="UTF-8"?>
<lint>
    <!-- If error allowed, this won't build because of an issue with OkIo (see: https://github.com/square/okio/issues/58) -->
    <!-- Similar issue also affects Butterknife (see: https://github.com/JakeWharton/butterknife/issues/110 ) -->
    <issue id="InvalidPackage" severity="ignore" />

    <!-- All below are issues that have been brought to informational (so they are visible, but don't break the build) -->
    <issue id="GradleDependency" severity="informational" />
    <issue id="OldTargetApi" severity="informational" />
</lint>

Here we reduce GradleDependency to an informational level, because we don't want to break the build every time Google bumps the version of a support library. We also did the same for OldTargetApi so that the build wouldn't break once we started testing with Android N (but weren't yet ready to target it). Last but not least, the top example is a case where we just don't have a choice and need to ignore an issue that is incorrectly inferred.

Conclusion

Android Lint is a very powerful tool. It will help prevent lots of bugs, from i18n issues to accessibility and from missing or unused resources to UI performance and more. Once you use it correctly, it will help you ship better apps with greater confidence. But to take advantage of its features you need to make sure you pay attention to what it reports. Make it part of your daily build, configure it correctly and you'll never go back.