Code Quality & Lint

Architecture and modularization organize where code lives. This lesson covers tools that catch problems inside the code itself — bugs, wasted resources, and inconsistent style — before they reach a user's device, using RecipeBox as the project being checked.

Android Lint

Android Lint ships built into the Android Gradle plugin — no extra dependency needed. It's a static analysis tool that scans a project's source without running it, looking for correctness bugs, performance issues, accessibility gaps, and unused resources.

Run from Gradle: the lint task analyzes the whole module.

./gradlew lint

Run from Android Studio: Analyze → Inspect Code… (or Analyze → Run Inspection by Name) runs the same checks with a UI for browsing results.

Reading a Lint Report

The Gradle lint task writes an HTML report (and an XML one) under each module's build/reports/ folder, grouped by severity and issue type.

app/build/reports/lint-results-debug.html
Explanation: Each finding names the specific rule (e.g., HardcodedText, UnusedResources), the exact file and line, a plain-language explanation, and usually a one-click "Show fix" suggestion in the Studio UI version of the same report.

A Real Lint Warning

A common one in a RecipeBox-style app: calling a location or camera API without first checking that the runtime permission was actually granted.

// Lint warning: MissingPermission
public void attachPhotoFromCamera() {
    cameraManager.openCamera(cameraId, stateCallback, null);  // flagged: permission not verified here
}
Explanation: Lint traces whether a permission check (like ContextCompat.checkSelfPermission()) provably guards the call. If it can't prove that, it flags the call — this is exactly the kind of bug that only shows up at runtime on a real device otherwise.

Suppressing a False Positive

Occasionally Lint flags something that's genuinely fine for a specific, understood reason. @SuppressLint silences a specific check on a specific method or class — it should never be reached for reflexively.

@SuppressLint("MissingPermission")
public void attachPhotoFromCamera() {
    // Permission is already verified one call up, in onRequestPermissionsResult()
    cameraManager.openCamera(cameraId, stateCallback, null);
}
Explanation: Scope @SuppressLint as narrowly as possible (one method, not a whole class or file) and leave a comment explaining why it's safe — a future reader (including a future you) has no way to know the check happens elsewhere without it.

Beyond Built-In Lint

A few optional tools are commonly added on top of Lint for teams that want more:

ToolFocus
CheckstyleEnforces a consistent code style (naming, formatting, import order)
PMDFlags suspicious patterns — unused variables, overly complex methods
SpotBugsAnalyzes compiled bytecode for likely bugs (null dereferences, resource leaks)
These are optional additions, each configured via its own Gradle plugin — a project doesn't need all three, or any of them, to benefit from Lint alone.

Code Review Habits

Tools catch what they're programmed to catch; a human reviewer catches everything else. A few habits that keep RecipeBox's codebase healthy as more people (or more time) touch it:

  • Consistent naming: RecipeRepository, not a mix of RecipeRepo, RecipesDataSource, and RecipeManager for the same concept.
  • Small, focused pull requests: a change that adds recipe search shouldn't also silently reformat unrelated files.
  • Read the diff as a reviewer would: does this method still do one thing? Does the name still describe what it does?
  • Treat a Lint warning in a diff as a discussion point, not something to auto-suppress to unblock a merge.

Hands-On Practice

  1. Run ./gradlew lint on a real project and open the generated HTML report.
  2. Fix one genuine warning it finds (not a suppression) — commonly an unused resource or a hardcoded string.
  3. Find one place where @SuppressLint might be legitimately needed, and write the comment explaining why.
Lint checks the code that's already written. The final lesson, Offline-First & Caching, looks at a design principle that shapes how RecipeBox's data layer is written in the first place.