| name | kotlin-android-reviewer |
| description | WHEN: Android Kotlin code review, Jetpack Compose patterns, Coroutines/Flow checks, ViewModel structure analysis
WHAT: Compose best practices + Coroutines patterns + State management + Memory leak detection + Performance optimization
WHEN NOT: KMP shared code → kotlin-multiplatform-reviewer, Backend → kotlin-spring-reviewer
|
Kotlin Android Reviewer Skill
Purpose
Reviews Android Kotlin code for Jetpack Compose, Coroutines, Flow, and ViewModel best practices.
When to Use
- Android Kotlin project code review
- "Compose pattern", "Coroutines", "Flow", "ViewModel" mentions
- Android performance, memory leak inspection
- Projects with Android plugin in
build.gradle.kts
Project Detection
com.android.application or com.android.library in build.gradle
AndroidManifest.xml exists
src/main/java or src/main/kotlin directories
Workflow
Step 1: Analyze Project
**Kotlin**: 1.9.x
**Compose**: 1.5.x
**minSdk**: 24
**targetSdk**: 34
**Architecture**: MVVM + Clean Architecture
Step 2: Select Review Areas
AskUserQuestion:
"Which areas to review?"
Options:
- Full Android pattern check (recommended)
- Jetpack Compose UI patterns
- Coroutines/Flow usage
- ViewModel/State management
- Memory leaks/Performance
multiSelect: true
Detection Rules
Jetpack Compose Patterns
| Check |
Recommendation |
Severity |
| Side-effect in Composable |
Use LaunchedEffect/SideEffect |
HIGH |
| Object creation without remember |
Use remember { } |
HIGH |
| Missing State hoisting |
Hoist state to parent |
MEDIUM |
| Missing derivedStateOf |
Use for derived state |
LOW |
| LazyColumn without key |
Add key parameter |
HIGH |
// BAD: Object creation without remember
@Composable
fun MyScreen() {
val list = mutableListOf<String>() // New every recomposition
}
// GOOD: Use remember
@Composable
fun MyScreen() {
val list = remember { mutableListOf<String>() }
}
// BAD: Direct suspend call in Composable
@Composable
fun MyScreen(viewModel: MyViewModel) {
viewModel.loadData() // Side-effect!
}
// GOOD: Use LaunchedEffect
@Composable
fun MyScreen(viewModel: MyViewModel) {
LaunchedEffect(Unit) {
viewModel.loadData()
}
}
Coroutines Patterns
| Check |
Recommendation |
Severity |
| GlobalScope usage |
Use viewModelScope/lifecycleScope |
CRITICAL |
| Missing Dispatcher |
Specify Dispatchers.IO/Default |
MEDIUM |
| Missing exception handling |
try-catch or CoroutineExceptionHandler |
HIGH |
| runBlocking abuse |
Convert to suspend function |
HIGH |
// BAD: GlobalScope
GlobalScope.launch {
repository.fetchData()
}
// GOOD: viewModelScope
viewModelScope.launch {
repository.fetchData()
}
// BAD: Network on Main
viewModelScope.launch {
val result = api.getData() // NetworkOnMainThreadException
}
// GOOD: IO Dispatcher
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
api.getData()
}
}
Flow Patterns
| Check |
Recommendation |
Severity |
| Direct collect in Composable |
Use collectAsState |
HIGH |
| SharedFlow without replay |
Set appropriate replay value |
MEDIUM |
| Nullable StateFlow initial |
Provide meaningful initial value |
MEDIUM |
// BAD: Direct collect in Composable
@Composable
fun MyScreen(viewModel: MyViewModel) {
var data by remember { mutableStateOf<Data?>(null) }
LaunchedEffect(Unit) {
viewModel.dataFlow.collect { data = it }
}
}
// GOOD: collectAsState
@Composable
fun MyScreen(viewModel: MyViewModel) {
val data by viewModel.dataFlow.collectAsState()
}
// BAD: Nullable StateFlow initial
private val _state = MutableStateFlow<UiState?>(null)
// GOOD: Sealed class with clear initial state
private val _state = MutableStateFlow<UiState>(UiState.Loading)
ViewModel Patterns
| Check |
Issue |
Severity |
| Direct Context reference |
Memory leak risk |
CRITICAL |
| View reference |
Memory leak risk |
CRITICAL |
| Missing SavedStateHandle |
Process death handling |
MEDIUM |
| Bidirectional data flow |
Use UiState + Event pattern |
MEDIUM |
// BAD: Activity Context reference
class MyViewModel(private val context: Context) : ViewModel()
// GOOD: Application Context with Hilt
class MyViewModel(
@ApplicationContext private val context: Context
) : ViewModel()
// BAD: Bidirectional binding
class MyViewModel : ViewModel() {
var name = MutableLiveData<String>()
}
// GOOD: Unidirectional + sealed class
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun onNameChanged(name: String) {
_uiState.update { it.copy(name = name) }
}
}
Memory Leak Detection
| Check |
Problem |
Solution |
| Inner class with outer reference |
Activity leak |
WeakReference or static |
| Unremoved Listener |
Memory leak |
Remove in onDestroy |
| Uncancelled Coroutine Job |
Job leak |
Structured concurrency |
| Unreleased Bitmap |
OOM risk |
recycle() or Coil/Glide |
Response Template
## Android Kotlin Code Review Results
**Project**: [name]
**Kotlin**: 1.9.x | **Compose**: 1.5.x
**Files Analyzed**: X
### Jetpack Compose
| Status | File | Issue |
|--------|------|-------|
| HIGH | ui/HomeScreen.kt | Object creation without remember (line 45) |
| MEDIUM | ui/ProfileScreen.kt | State hoisting recommended |
### Coroutines/Flow
| Status | File | Issue |
|--------|------|-------|
| CRITICAL | data/Repository.kt | GlobalScope usage (line 23) |
| HIGH | viewmodel/MainViewModel.kt | Missing exception handling |
### ViewModel/State
| Status | File | Issue |
|--------|------|-------|
| HIGH | viewmodel/DetailViewModel.kt | Activity Context reference |
### Recommended Actions
1. [ ] GlobalScope → viewModelScope
2. [ ] Add remember { }
3. [ ] Apply UiState sealed class pattern
Best Practices
- Compose: Prefer Stateless Composable, State hoisting
- Coroutines: Structured concurrency, appropriate Dispatcher
- Flow: Distinguish Hot/Cold Flow, StateFlow for UI state
- ViewModel: Unidirectional data flow, avoid Context
- Testing: Turbine for Flow, Compose test rules
Integration
code-reviewer skill: General Kotlin code quality
kotlin-multiplatform-reviewer skill: KMP shared code
test-generator skill: Android test generation
Notes
- Based on Compose 1.0+
- Kotlin 1.9+, Coroutines 1.7+ recommended
- Supports Hilt/Dagger DI patterns