Android Lint and Kotlin Warnings
Catch real platform problems early, without making CI miserable
Android Lint and Kotlin Warnings
Catch real platform problems early, without making CI miserable
← Part 2: Detekt Done Right | Part 3 | Part 4: Tests That Scale →
Detekt is great at code smells and complexity. Android Lint is different. It’s closer to a platform safety net.
Lint catches problems that static Kotlin analysis often can’t, like:
- Incorrect resource usage
- Broken accessibility patterns
- Lifecycle mistakes
- Manifest and permission issues
- API level pitfalls
- Subtle performance traps
If you want a modern Android CI/CD setup, Lint is not optional. The key is to run it in a way that stays fast, stays relevant, and produces reports people will actually read.
Where Lint sits in the blueprint
From Part 0, Lint belongs in the PR pipeline gates.
- PR pipeline: run a fast, practical lint check (usually debug)
- Main pipeline: run a deeper lint check, often including release
- Release pipeline: lint should be boring by then
The goal is predictable signal early. You don’t want “lint surprises” the week you’re trying to ship.
Which Lint task should you run?
Android Gradle Plugin gives you multiple tasks. The two that matter most:
lintDebug
Faster feedback, closer to dev builds, good for PRs.
lintRelease
More strict in some setups, closer to what you ship, good for main and release readiness.
If you’re starting out, the simplest approach is:
- PR:
lintDebug - Main:
lintDebugandlintRelease(if it's not too slow)
As your project scales, you can refine this by module, by variant, or by feature.
The report problem: make output usable
Lint is only helpful if developers can see the result quickly.
In CI, always publish:
- HTML report for humans
- XML report for tooling
- SARIF report if you want results visible in GitHub UI
A typical lint report ends up under something like:
**/build/reports/lint-results-*.html
Even if your exact path differs, the principle stays the same: upload build reports as artifacts so nobody is digging through CI logs.
A clean Lint configuration baseline
You don’t need a huge Lint setup to start getting value. The goal is consistency and a stable pipeline.
In your module build.gradle.kts:
android {
lint {
abortOnError = true
warningsAsErrors = false
htmlReport = true
xmlReport = true
sarifReport = true
// Keep it focused
checkGeneratedSources = false
checkDependencies = false
}
}
Notes on the defaults:
abortOnError = truemakes lint a real quality gatewarningsAsErrorsshould be introduced carefully (more on that below)checkDependenciescan be expensive in large builds—turn it on later if needed
Warnings as errors, without chaos
Turning all warnings into errors sounds noble. In practice, it can create a noisy rollout and slow teams down.
A better progression looks like this:
Stage 1: Errors block, warnings inform
- Treat lint errors as merge blockers
- Keep warnings visible, but don’t fail CI on them yet
Stage 2: Tighten the screws slowly
- Pick a small set of warning categories that you care about
- Promote those to errors
- Keep the rest as warnings
Stage 3: Enforce stricter policies on new code
If your build supports it, you can create policies that prevent new warnings without forcing the team to fix the entire backlog in one week.
The goal is improvement with momentum, not a stalled pipeline.
Baselines, but don’t abuse them
Lint supports baseline files. They exist for the same reason Detekt baselines exist: large existing codebases.
A baseline can be useful when:
- You’re adopting lint rules in an older project
- You want to prevent new issues first
- You want to clean old issues gradually
A baseline becomes harmful when:
- It keeps growing forever
- It becomes a way to avoid fixing issues
- It gets regenerated casually to silence CI
Treat baselines as temporary scaffolding, not permanent architecture.
Kotlin compiler warnings are part of quality too
Lint catches Android-specific issues. Kotlin warnings catch language-level issues, including:
- Unchecked casts
- Deprecated API usage
- Experimental API usage leaks
- Null safety warnings
Two main approaches teams use:
Approach A: Compiler warnings visible, but not blocking
This keeps momentum. Warnings show up in logs and get handled over time.
Approach B: Warnings as errors, scoped
This is stricter, but must be introduced carefully, or it will break builds constantly.
In a modern Android project, a practical stance is:
- Make warnings highly visible
- Enforce warnings as errors in critical modules first (domain, data)
- Loosen in UI modules if needed, then tighten later
You’re aiming for correctness, not punishment.
Compose and Lint: pay attention to accessibility
Compose moves a lot of UI logic into Kotlin. That makes Lint and accessibility checks even more important.
Good targets for early enforcement:
- Content descriptions for meaningful icons
- Touch target sizes
- Semantics where needed
- Avoiding clickable items without proper labels
If your app is heading toward “real user utility,” accessibility is not optional polish. It’s product quality.
CI commands that work well
For PR checks, keep it lean:
./gradlew spotlessCheck detekt lintDebug test
For main checks, add stronger coverage:
./gradlew spotlessCheck detekt lintDebug test
If lint starts taking too long, split jobs and run them in parallel. Don’t drop lint. Speed should be solved with caching and concurrency, not by removing guardrails.
Performance tips that keep Lint sane
Lint can become slow in big Android repos. A few practical moves help:
- Run
lintDebugon PR, not full release lint every time - Avoid scanning generated sources unless you really need it
- Keep Gradle caching enabled in CI
- Avoid running lint multiple times across workflows
- Consider limiting lint scope by module in very large builds
Lint should remain a credible PR gate — fast enough that people respect it.
Definition of done for Part 3
You’re done when:
✓ lintDebug runs on every PR and blocks merge on lint errors
✓ Lint reports are uploaded as CI artifacts and easy to open
✓ Main runs deeper lint checks, including release if feasible
✓ Lint baselines are used only as a temporary bridge
✓ Kotlin warnings are visible and trending down over time
Once Lint is stable, you have a serious quality gate stack. Next we move into the part that makes pipelines feel “real” to engineers.
Next: Part 4 — Tests that scale (without flakiness)
Part 4 will cover:
- Unit tests as the core PR gate
- How to add instrumented tests without making CI unreliable
- Gradle Managed Devices and parallelization
- Strategies for flaky tests and quarantining
Because the only thing worse than no tests is tests that fail randomly and teach your team to ignore red builds.
Series Navigation:
← Part 2: Detekt Done Right | Part 3: Android Lint and Kotlin Warnings | Part 4: Tests That Scale →
메타데이터
- post_id
- 5771fce3e6ce
- slug
- part-3-android-lint-and-kotlin-warnings-5771fce3e6ce
- url
- https://medium.com/@androidmeda/part-3-android-lint-and-kotlin-warnings-5771fce3e6ce
- canonical_url
- https://medium.com/@androidmeda/part-3-android-lint-and-kotlin-warnings-5771fce3e6ce
- author_url
- https://medium.com/@androidmeda
- status
- ok
- fetched_at
- 2026-06-09 14:34:10