Android Code Review Checklist
Rapid-scan checklist for Android code reviews. Use this to quickly identify issues, then dive into linked documentation for details.
Security Red Flags - Block PR Immediately
- Plain SharedPreferences for secrets: Tokens, API keys in plain SharedPreferences → Use EncryptedSharedPreferences
- Hardcoded secrets: API keys, passwords in source code → Use BuildConfig or secure storage
- SQL injection: String concatenation in SQL/Room queries → Use parameterized queries
- No certificate pinning: Production API without certificate pinning → Certificate Pinning
- Sensitive data in logs: Tokens, account numbers, PINs in Logcat → Never log sensitive data
- Weak biometric authenticators:
BIOMETRIC_WEAKinstead ofBIOMETRIC_STRONG - ProGuard/R8 disabled: Release builds without obfuscation
- Root/jailbreak detection bypassed: No SafetyNet/Play Integrity checks for financial transactions
- Screenshots enabled on sensitive screens: No
FLAG_SECUREon payment/auth screens - Network cleartext traffic allowed:
usesCleartextTraffic="true"in manifest
Common Mistakes
Architecture Violations
- Business logic in ViewModel instead of Use Cases → Clean Architecture
- Android dependencies in domain layer (
android.*imports) → Domain layer must be pure Kotlin - Repository implementations in domain layer → Interfaces in domain, implementations in data
- Direct API/DAO calls from ViewModel (skip use cases) → Use Cases orchestrate logic
- DTOs exposed to UI layer instead of domain models → Mapper pattern
- Mutable StateFlow exposed publicly → Private mutable, public immutable
Dependency Injection (Hilt)
- Field injection instead of constructor injection
@Singletonscope for stateful objects (leaks)- Manual instantiation of dependencies (bypasses Hilt)
- Wrong component scope (
SingletonComponentfor ViewModels) - Missing
@HiltViewModelannotation on ViewModels - Not using
@Inject constructorfor automatic provision
Jetpack Compose Issues
- Reading state in composition without
remember(recomposition leak) - Side effects not in
LaunchedEffect/DisposableEffect→ Compose lifecycle - ViewModels created inline (
= ViewModel()) instead ofhiltViewModel() - Large composables (>100 lines) without decomposition
- Missing keys in
LazyColumnitems (poor scroll performance) - Unstable data classes (non-immutable) causing excessive recomposition
- Navigation in ViewModel instead of composable → Navigation patterns
Kotlin Issues
- Nullable types without null checks
- Mutable collections exposed (
MutableListreturn type) - Blocking calls on main thread (
runBlockingin UI code) - Not using
suspendfor async operations - Ignoring coroutine cancellation (
CancellationExceptioncaught) - GlobalScope usage (leaks) → Use
viewModelScopeorlifecycleScope !!operator (force unwrap) without justification
Watch For - Performance & Correctness
Coroutines & Flow
- Collecting Flow without lifecycle awareness (
collectvscollectAsState) → Flow best practices - Missing
flowOnfor background work - Hot Flow (
StateFlow) for one-time events → Use Channel for events - Exception handling missing in Flow (
catchoperator) - Not cancelling coroutines properly (leaks)
- Using
GlobalScopeinstead of structured concurrency
Compose Performance
- Heavy computation in composition (not in
derivedStateOf) - Creating new lambdas in composition (not
remember) - Large lists not using
LazyColumn/LazyRow - Missing
keyparameter inLazyColumnitems - Unnecessary recompositions (unstable classes)
- Not using
@Stableor@Immutableannotations
Room Database
- Blocking queries on main thread (
SELECTnot suspended) - Missing database migrations
- Entities with mutable fields
@Daomethods not suspend for long operations- No indices on frequently queried columns → Room optimization
Memory Leaks
- Activity/Fragment references in ViewModels
- Context stored in singletons (use Application context)
- Listeners not removed in
onDestroy - Bitmap not recycled after use
lateinitproperties never initialized (crash risk)
Best Practices - Code Quality
Clean Architecture
- Domain layer is pure Kotlin (no Android dependencies) → Architecture layers
- Repository interfaces in domain, implementations in data → Repository pattern
- Use cases encapsulate business logic → Use Cases
- Mappers between layers (DTO ↔ Domain ↔ Entity) → Mapper pattern
- Unidirectional data flow (events up, state down) → UDF
Modern Android
- Jetpack Compose for UI (not XML layouts) → Compose guidelines
- Material Design 3 theming → Material 3
- Kotlin Coroutines and Flow for async → Coroutines
- Hilt for dependency injection → Hilt setup
- Room for local persistence → Room
- Retrofit for API calls → Retrofit
State Management
StateFlowfor UI state → StateFlow pattern@Publishedproperties in ViewModels- Sealed interfaces for screen states (Loading/Success/Error) → State patterns
- Events via Channel for one-time actions
- State hoisting in Compose
Security
- EncryptedSharedPreferences for tokens → Secure storage
- Biometric authentication with LocalAuthentication → Biometric
- Certificate pinning for production APIs → Certificate pinning
- ProGuard/R8 for release builds → Obfuscation
- No sensitive data in logs
Quick Scan Table
| Category | What to Check | Severity | Reference |
|---|---|---|---|
| Security | No plain SharedPreferences for secrets | Block | EncryptedSharedPreferences |
| Security | No hardcoded API keys | Block | Security |
| Security | Certificate pinning implemented | Block | Certificate Pinning |
| Security | No sensitive data in logs | Block | Security |
| Security | ProGuard/R8 enabled for release | Block | Obfuscation |
| Architecture | No Android imports in domain | Block | Clean Architecture |
| Architecture | Use cases encapsulate logic | Fix | Use Cases |
| Architecture | Repository pattern implemented | Fix | Repositories |
| Architecture | No business logic in ViewModel | Fix | MVVM |
| DI (Hilt) | Constructor injection used | Fix | Hilt |
| DI (Hilt) | @HiltViewModel on ViewModels | Fix | Hilt ViewModels |
| Compose | Side effects in LaunchedEffect | Fix | Compose Lifecycle |
| Compose | ViewModels via hiltViewModel() | Fix | Hilt Integration |
| Compose | Key parameter in LazyColumn | Fix | Performance |
| Kotlin | No !! operator | Review | Kotlin |
| Kotlin | Suspend for async operations | Fix | Coroutines |
| Kotlin | No GlobalScope usage | Fix | Coroutines |
| Flow | Collect with lifecycle awareness | Fix | Flow |
| Flow | Exception handling in Flow | Fix | Flow |
| Room | Queries are suspended | Fix | Room |
| Room | Database migrations defined | Fix | Room Migrations |
Legend:
- Block PR - Security vulnerability or architecture violation
- Fix Before Merge - Correctness issue, performance problem, or likely bug
- Review & Discuss - Code quality improvement
Additional Considerations
Gradle Configuration
- Minimum SDK appropriate (API 24+ recommended)
- Target SDK is latest stable
- Kotlin version consistent across modules
- Dependencies up-to-date (AndroidX, Compose)
- Version catalog used for dependency management
- Build variants configured (debug/release)
Testing
- Unit tests for ViewModels → Testing
- Unit tests for Use Cases with fake repositories
- Compose UI tests for screens
- Instrumentation tests for Room DAOs
- TestContainers for API integration tests
- Turbine for Flow testing
- MockK for Kotlin mocking
Accessibility
- Content descriptions on images/icons
- Semantic properties for Compose components
- Proper heading hierarchy
- Touch targets minimum 48dp
- Color contrast ratio 4.5:1 minimum
Localization
- All strings in
strings.xml(no hardcoded text) - Plural resources for counts
- RTL layout support
- Date/time formatting with locale
- Currency formatting appropriate
Block Checklist - Must Fix Before Merge
- No plain SharedPreferences for secrets (use EncryptedSharedPreferences)
- No hardcoded API keys or credentials
- Certificate pinning implemented for production
- No sensitive data in Logcat
- ProGuard/R8 enabled for release builds
- No Android dependencies in domain layer
- Use cases encapsulate business logic
- Repository pattern properly implemented
- Hilt dependency injection used correctly
- Compose side effects in proper lifecycle hooks
- No blocking calls on main thread
- Coroutines properly scoped (no GlobalScope)
- Room queries are suspended
- No memory leaks (Context/Activity refs in ViewModels)
Android-Specific Testing Checklist
Before approving Android PRs:
- Unit tests for ViewModels → Android Testing
- Unit tests for Use Cases with fake repositories
- Compose UI tests for critical user paths
- Room DAO tests (instrumentation or in-memory)
- Flow tests with Turbine
- Edge cases tested (null, empty, error states)
- Coroutine tests with runTest
- No flaky tests (tests pass consistently)
- Test coverage meets minimum threshold (80%+)
- Mutation testing passing (if configured)
Related Documentation
Android Framework Guides:
- Android Overview - Project setup, Hilt configuration
- Android Architecture - Clean Architecture, MVVM, patterns
- Android UI - Jetpack Compose, Material Design 3
- Android Data - Retrofit, Room, repositories
- Android Security - EncryptedSharedPreferences, biometrics
- Android Testing - Testing strategies
- Android Performance - Optimization, profiling
Kotlin Language:
- Kotlin General - Kotlin best practices
- Kotlin Concurrency - Coroutines, Flow
- Kotlin Testing - Testing patterns
Process: