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.

Stay in the loop with our latest content!

Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

6 Tips for Facilitating Effective Roadmapping and Story Mapping Sessions

6 Tips for Facilitating Effective Roadmapping and Story Mapping Sessions

June 28, 2019

Roadmapping and story mapping are effective ways to align teams around a set direction. It can even help facilitate future conversations of prioritization and backlog grooming.

Read more
Are You Solving the Right Problem?
Team

Are You Solving the Right Problem?

February 26, 2020

In Art’s Principles, renowned architect Art Gensler described part of his Design Thinking consultation process as “finding the right problem to solve.” This resonated with me, particularly as I’ve thought about how Michigan Software Labs interacts with clients in the Product Strategy Phase; something I believe sets us apart from other software development companies.

Read more
Chicago Roboto 2020: A Virtual Success
Android

Chicago Roboto 2020: A Virtual Success

January 19, 2021

Looking back on last year’s Chicago Roboto, it was a great opportunity to learn while spending time hanging out with teammates. So, when it was announced that this year’s event was going to be virtual, it was hard not to be disappointed. Then again, with so many great speakers lined up, there was still plenty to look forward to.

Read more
View more articles