MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Learn how to use Claude Code's hook system to create automated quality gates for your Android projects, catching bugs and enforcing standards before code hits your repository.
On this page
You're shipping Android apps solo, juggling 22 projects while maintaining quality. Waiting for CI builds to fail on basic issues wastes hours you could spend coding. Claude Code Hooks let you intercept problems at the source, creating automated quality gates that run instantly in your terminal.
As a solo Android developer managing multiple apps, I've seen too many hours lost to preventable issues:
Traditional CI/CD pipelines catch these issues, but only after you've pushed code. By then, context switches cost productivity. Quality gates should run locally, instantly, and automatically.
Claude Code Hooks solve this by letting you define scripts that execute at specific lifecycle events—before file writes, after commands, or when you start a new conversation.
Let's create a hook that validates Kotlin code quality before files are saved.
First, install the hooks extension:
claude add hooksThis adds the
@claude-hookshooks.jsonCreate a hook that runs ktlint before any Kotlin file is written:
{
"hooks": [
{
"event": "pre-write",
"matcher": "**/*.kt",
"command": "ktlint --fail --print {}",
"description": "Format and validate Kotlin files"
}
]
}[!TIP] The
placeholder passes the filename to your command. Use it to target specific files.code{}
Now every time you edit a
.ktLet's create a more sophisticated hook that validates Android architecture patterns.
I created this hook to enforce dependency direction in my MVVM architecture:
// scripts/validate-architecture.kts
#!/usr/bin/env kotlin
val viewModelPattern = Regex("""class \w+ViewModel.*:\s*ViewModel""")
val repositoryPattern = Regex("""class \w+Repository""")
val dataSourcePattern = Regex("""class \w+DataSource""")
val viewModelFiles = File(".").walk().filter { it.path.endsWith("ViewModel.kt") }
val repositoryFiles = File(".").walk().filter { it.path.endsWith("Repository.kt") }
viewModelFiles.forEach { vmFile ->
vmFile.readText().let { content ->
if (content.contains("new ") && !content.contains("Repository")) {
println("❌ ${vmFile.path}: ViewModel should not instantiate dependencies directly")
System.exit(1)
}
}
}Register this in your hooks configuration:
{
"hooks": [
{
"event": "pre-write",
"matcher": "**/*ViewModel.kt",
"command": "kotlinc scripts/validate-architecture.kts",
"description": "Validate ViewModel architecture rules"
}
]
}Another powerful hook validates that new feature files have corresponding tests:
#!/bin/bash
# scripts/require-tests.sh
FILE_PATH="$1"
FEATURE_NAME=$(basename "$FILE_PATH" | sed 's/\..*//')
TEST_FILE="src.test.java/com/example/$FEATURE_NAMETest.java"
if [[ ! -f "$TEST_FILE" ]]; then
echo "⚠️ No test found for $FILE_PATH"
echo "Create: $TEST_FILE"
exit 1
fiThis catches missing test coverage before you commit.
Hooks don't replace CI—they complement it. Use hooks for fast feedback and CI for comprehensive validation.
| Check Type | Local Hook | CI Pipeline |
|---|---|---|
| Format validation | ~0.2s | ~45s |
| Unit tests | ~3s | ~2m |
| API validation | ~0.5s | ~1m |
| Security scan | Not feasible | ~3m |
[!NOTE] Hooks excel at immediate feedback. Reserve heavy lifting for CI.
Here's a post-command hook that runs after successful builds:
{
"hooks": [
{
"event": "post-command",
"matcher": "gradle build",
"command": "./gradlew test --console=plain",
"description": "Run tests after successful build"
}
]
}With Claude Code's AI capabilities, hooks can provide intelligent feedback.
#!/usr/bin/env python3
import sys
import re
def detect_code_smells(content):
smells = []
# Detect large functions
functions = re.findall(r'fun \w+\([^)]*\)\s*[^{]*\{([^}]*)\}', content, re.DOTALL)
for i, func in enumerate(functions):
if len(func.split('\n')) > 25:
smells.append(f"Function #{i+1} is too long ({len(func.split(chr(10)))} lines)")
# Detect TODOs
todos = re.findall(r'//\s*TODO:', content)
smells.extend([f"TODO found: {todo}" for todo in todos])
return smells
if __name__ == "__main__":
with open(sys.argv[1], 'r') as f:
content = f.read()
smells = detect_code_smells(content)
if smells:
print("Code smells detected:")
for smell in smells:
print(f" ⚠️ {smell}")
sys.exit(1)This provides immediate architectural feedback without waiting for external tools.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read