Android Development Process

The Lowdown on Android Lint

August 13, 2019

I’m a big fan of using auto­mat­ed tools to improve the qual­i­ty of code our teams write. Often this includes unit test­ing and code for­mat check­ers. But, for the pur­pos­es of this blog, I’m going to cov­er one kind of tool in par­tic­u­lar: lin­ters. Though some tools con­flate lint­ing with code style check­ing, in real­i­ty a lin­ter is tool that ana­lyzes code for poten­tial bugs or unde­sir­able behavior.

(Quick note: While this post focus­es specif­i­cal­ly on lint­ing Android source code, keep an eye out for future posts on lint­ing for oth­er frame­works and languages.)

Elim­i­nat­ing Bugs with Android Lint #

The folks on the Android Devel­op­er Tools team have giv­en us a great tool for lint­ing Android code, one that’s inte­grat­ed direct­ly into the build sys­tem. In fact, you are already using this tool and prob­a­bly don’t even know it! Some of the checks it includes run by default when you are edit­ing Java or Kotlin source code or XML lay­out resources in a project. One exam­ple is when you don’t use an @string resource for user-vis­i­ble text in a lay­out resource file:

There is also a sub­set of the avail­able lint rules that check for issues which cause run­time crash­es if not addressed, such as call­ing APIs that are unavail­able on old­er plat­forms with­out an SDK_INT check or try­ing to start an Activity that isn’t reg­is­tered in the AndroidManifest.xml file. These vital” checks run auto­mat­i­cal­ly as part of a release build in the lintVitalRelease gra­dle task.

It’s great get­ting all of these tools for free. Then again, humans are eas­i­ly dis­tract­ed and don’t always pay atten­tion to appro­pri­ate warn­ings when edit­ing source files. Wouldn’t it be nice if these checks ran auto­mat­i­cal­ly every time we com­mit changes to our source repos?

Run­ning Android Lint in CI #

At Michi­gan­Labs, our Android projects run lint as part of the Git­lab CI pipeline that is trig­gered on every new com­mit to our Git repo. We also retain the HTML and XML arti­facts from lint so the devel­op­er doesn’t have to re-run the lint tool in order to see the for­mat­ted results, not to men­tion the lengthy error report:

Lint:
  script:
    - ./gradlew lintDebug
  artifacts:
    name: lint-$CI_COMMIT_SHA
    paths:
      - ./**/build/reports/lint-results-debug.html
      - ./**/build/reports/lint-results-debug.xml
    expire_in: 1 month

We also enable Pipelines must suc­ceed” in the merge request checks sec­tion of the repos­i­to­ry set­tings to pre­vent merge requests with fail­ing tests or lint checks from being merged.

Cus­tomiz­ing Lint Checks #

By default, the lin­ter will only fail if there are error sever­i­ty lev­el issues found in your project. There are numer­ous oth­er checks that default to a lev­el of warning (or below) which, if detect­ed, would ide­al­ly fail a build. We can do this using a cus­tomized lint con­fig­u­ra­tion file. The lint.xml file con­tains a list of issues as well as the lev­el of sever­i­ty vio­la­tions should be report­ed at. To make the hard­cod­ed text warn­ing into an error, sim­ply insert <issue id="HardcodedText" severity="error" /> into the file.

Below is the lint.xml file we cur­rent­ly use for our projects that should offer a base­line for your projects. The Android team main­tains a list of avail­able checks, also vis­i­ble in the Android Stu­dio pref­er­ences dia­log under Edi­tor > Inspec­tions > Android > Lint”.

<lint>
    <issue id="ObsoleteSdkInt" severity="error"/>
    <issue id="PrivateResource" severity="error"/>
    <issue id="UnusedIds" severity="ignore"/>  <!-- does not catch uses of view IDs by Kotlin android extensions -->
    <issue id="UnusedResources" severity="ignore" />
    <issue id="GradleDynamicVersion" severity="error" />
    <issue id="CheckResult" severity="error" />
    <issue id="SimpleDateFormat" severity="error" />
    <issue id="RtlEnabled" severity="error" />
    <issue id="Registered" severity="error" />
    <issue id="InnerclassSeparator" severity="error" />
    <issue id="CommitPrefEdits" severity="error" />
    <issue id="HardcodedText" severity="error" />
    <issue id="UnusedAttribute" severity="error" />
    <issue id="IconLocation" severity="error" />
    <issue id="MergeRootFrame" severity="error"/>
    <issue id="UseCompoundDrawables" severity="error"/>
    <issue id="Autofill" severity="error" />
    <issue id="LabelFor" severity="error" />

    <!-- Change back to error when https://issuetracker.google.com/issues/110243369 is fixed -->
    <issue id="SyntheticAccessor" severity="ignore" />

    <issue id="MissingTranslation" severity="ignore" />
    <issue id="ExtraTranslation" severity="ignore" />

    <!-- No one cares. -->
    <issue id="Overdraw" severity="ignore" />
    <issue id="GoogleAppIndexingWarning" severity="ignore"/>
    <issue id="Typos" severity="ignore" />

    <!-- This is nice to have, but it makes your builds not repeatable in the future -->
    <issue id="GradleDependency" severity="informational" />
</lint>

If the lint.xml file appears in the root direc­to­ry of the project, it is applied auto­mat­i­cal­ly. Our teams often go a step fur­ther and add options that lead to more thor­ough checks, mak­ing the out­put more effec­tive in CI logs. These options are con­fig­ured in your module’s build.gradle inside the android section:

android {
  lintOptions {
    // Write a text report to the console (Useful for CI logs)
    textReport true
    textOutput 'stdout'
    // HTML/XML reports always explain but this gets too verbose in console logs
    explainIssues false

    // Include the resources from all of its upstream modules in its analysis.
    // Required to get accurate unused resource
    checkDependencies true

    // Also check test case code for lint issues
    checkTestSources true

    // We run a full lint analysis as build part in CI, so we can skip redundant checks.
    checkReleaseBuilds false
  }
  // ...
}

Get­ting Exist­ing Projects Up To Speed #

You may find that adding this set­up to your exist­ing projects leads to hun­dreds or even thou­sands of errors. By baselin­ing the project, you can cap­ture a snap­shot of the exist­ing errors and, more impor­tant­ly, weed out new ones. This makes adding Android lint checks to your project vir­tu­al­ly cost-free.

When edit­ing files in Android Stu­dio, the base­line con­fig­u­ra­tion is ignored, allow­ing you to iden­ti­fy and address old errors. My advice is to fix the lint errors in files that you are already touch­ing for oth­er rea­sons, allow­ing you to address issues in the base­line file down the road.

Extend­ing Lint Checks to Oth­er APIs #

Libraries, such as Tim­ber and Work­Man­ag­er ship their own cus­tom lint rules to avoid incor­rect or inef­fi­cient usage of their APIs. The best part is that Android lint auto­mat­i­cal­ly detects these new rules and applies them to your build. Note that you may still need to tweak the sever­i­ty set­tings on the new rules so that, if vio­lat­ed, your build will fail.

You can also pro­vide your own lint rules as the devel­op­er of an Android library. I won’t cov­er that in this post, but Big Nerd Ranch offers insight on how to write your own cus­tom lint checks.

Tak­ing the Pain Out of the Dev Cycle #

Some of the issues that Android lint checks for can take a while to ana­lyze (such as unused resources requir­ing checkDependencies to be enabled); how­ev­er, run­ning the checks in the back­ground after a com­mit in CI is a good way to take the pain out of the local devel­op­ment cycle.

Look­ing ahead, the Android tools team has been work­ing on Project Mar­ble, a way to improve the per­for­mance of the lin­ter and oth­er build tools. We can’t wait.

Looking for more like this?

Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.

Lessons Learned from our Associate Developer
Team

Lessons Learned from our Associate Developer

September 13, 2023

One of our Associate Software Developers, Rohit, reflects on his time at MichiganLabs working on a short-term project, what he learned about real-world development, and the software consultancy business model.

Read more
How to Prepare for our Associate Software Developer Position
Team

How to Prepare for our Associate Software Developer Position

June 30, 2023

Tips for applying to MichiganLab's Associate Software Developer program

Read more
To RFP or not to RFP?
Business Process

To RFP or not to RFP?

January 19, 2024

Selecting the right digital product partner is not just about ticking boxes in a request for proposal (RFP). It’s more important to engage in a meaningful discovery process to find a collaborator who can turn your vision into a successful digital reality. Here are three things to watch out for as you search for the perfect digital collaborator.

Read more
View more articles