Android Development Process

The Lowdown on Android Lint

August 13, 2019

I’m a big fan of using automated tools to improve the quality of code our teams write. Often this includes unit testing and code format checkers. But, for the purposes of this blog, I’m going to cover one kind of tool in particular: linters. Though some tools conflate linting with code style checking, in reality a linter is tool that analyzes code for potential bugs or undesirable behavior.

(Quick note: While this post focuses specifically on linting Android source code, keep an eye out for future posts on linting for other frameworks and languages.)

Eliminating Bugs with Android Lint

The folks on the Android Developer Tools team have given us a great tool for linting Android code, one that’s integrated directly into the build system. In fact, you are already using this tool and probably don't even know it! Some of the checks it includes run by default when you are editing Java or Kotlin source code or XML layout resources in a project. One example is when you don't use an @string resource for user-visible text in a layout resource file:

There is also a subset of the available lint rules that check for issues which cause runtime crashes if not addressed, such as calling APIs that are unavailable on older platforms without an SDK_INT check or trying to start an Activity that isn’t registered in the AndroidManifest.xml file. These “vital” checks run automatically as part of a release build in the lintVitalRelease gradle task.

It’s great getting all of these tools for free. Then again, humans are easily distracted and don’t always pay attention to appropriate warnings when editing source files. Wouldn’t it be nice if these checks ran automatically every time we commit changes to our source repos?

Running Android Lint in CI

At MichiganLabs, our Android projects run lint as part of the Gitlab CI pipeline that is triggered on every new commit to our Git repo. We also retain the HTML and XML artifacts from lint so the developer doesn’t have to re-run the lint tool in order to see the formatted results, not to mention the lengthy error report:

`yml Lint: script:

- ./gradlew lintDebug


name: lint-$CI_COMMIT_SHA
  - ./**/build/reports/lint-results-debug.html
  - ./**/build/reports/lint-results-debug.xml
expire_in: 1 month


We also enable "Pipelines must succeed" in the merge request checks section of the repository settings to prevent merge requests with failing tests or lint checks from being merged.

Customizing Lint Checks

By default, the linter will only fail if there are error severity level issues found in your project. There are numerous other checks that default to a level of warning (or below) which, if detected, would ideally fail a build. We can do this using a customized lint configuration file. The lint.xml file contains a list of issues as well as the level of severity violations should be reported at. To make the hardcoded text warning into an error, simply insert <issue id="HardcodedText" severity="error" /> into the file.

Below is the lint.xml file we currently use for our projects that should offer a baseline for your projects. The Android team maintains a list of available checks, also visible in the Android Studio preferences dialog under “Editor > Inspections > Android > 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 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" />


If the lint.xml file appears in the root directory of the project, it is applied automatically. Our teams often go a step further and add options that lead to more thorough checks, making the output more effective in CI logs. These options are configured in your module’s build.gradle inside the android section:

`groovy 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

} // ... } `

Getting Existing Projects Up To Speed

You may find that adding this setup to your existing projects leads to hundreds or even thousands of errors. By baselining the project, you can capture a snapshot of the existing errors and, more importantly, weed out new ones. This makes adding Android lint checks to your project virtually cost-free.

When editing files in Android Studio, the baseline configuration is ignored, allowing you to identify and address old errors. My advice is to fix the lint errors in files that you are already touching for other reasons, allowing you to address issues in the baseline file down the road.

Extending Lint Checks to Other APIs

Libraries, such as Timber and WorkManager ship their own custom lint rules to avoid incorrect or inefficient usage of their APIs. The best part is that Android lint automatically detects these new rules and applies them to your build. Note that you may still need to tweak the severity settings on the new rules so that, if violated, your build will fail.

You can also provide your own lint rules as the developer of an Android library. I won’t cover that in this post, but Big Nerd Ranch offers insight on how to write your own custom lint checks.

Taking the Pain Out of the Dev Cycle

Some of the issues that Android lint checks for can take a while to analyze (such as unused resources requiring checkDependencies to be enabled); however, running the checks in the background after a commit in CI is a good way to take the pain out of the local development cycle.

Looking ahead, the Android tools team has been working on Project Marble, a way to improve the performance of the linter and other build tools. We can’t wait.

Josh Friend
Josh Friend
Development Practice Co-Lead

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.

Growing with Glue Work

Growing with Glue Work

July 21, 2021

Co-Written By Kylie Martinez and Amanda Clouser

Read more
Putting a Kettle On: Server-Side Swift with Vapor

Putting a Kettle On: Server-Side Swift with Vapor

March 23, 2020

One of my goals this year is to make a concerted effort to try out Vapor for server development. As a framework for making server applications in Swift, I’ve had my eye on Vapor since the 3.0 beta period many moons ago. The release of Vapor 4 seemed like as good a time as any to dive back in.

Read more
The Measure Of Success: Staying On Track From The Very Beginning

The Measure Of Success: Staying On Track From The Very Beginning

July 6, 2020

Any successful project begins with a clearly defined problem to solve (Are You Solving the Right Problem?). Project stakeholders need to be able to articulate the overriding challenge succinctly from the very start. (Spoiler: this often involves narrowing your vision, and may include…gasp…removing functionality.)

Read more
View more articles