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
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Reviews Android Kotlin code for Jetpack Compose, Coroutines, Flow, and ViewModel best practices.
build.gradle.ktscom.android.application or com.android.library in build.gradleAndroidManifest.xml existssrc/main/java or src/main/kotlin directories**Kotlin**: 1.9.x
**Compose**: 1.5.x
**minSdk**: 24
**targetSdk**: 34
**Architecture**: MVVM + Clean Architecture
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
| 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()
}
}
| 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()
}
}
| 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)
| 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) }
}
}
| 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 |
## 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
code-reviewer skill: General Kotlin code qualitykotlin-multiplatform-reviewer skill: KMP shared codetest-generator skill: Android test generation