--- 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 | ```kotlin // BAD: Object creation without remember @Composable fun MyScreen() { val list = mutableListOf() // New every recomposition } // GOOD: Use remember @Composable fun MyScreen() { val list = remember { mutableListOf() } } // 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 | ```kotlin // 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 | ```kotlin // BAD: Direct collect in Composable @Composable fun MyScreen(viewModel: MyViewModel) { var data by remember { mutableStateOf(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(null) // GOOD: Sealed class with clear initial state private val _state = MutableStateFlow(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 | ```kotlin // 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() } // GOOD: Unidirectional + sealed class class MyViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow = _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 1. **Compose**: Prefer Stateless Composable, State hoisting 2. **Coroutines**: Structured concurrency, appropriate Dispatcher 3. **Flow**: Distinguish Hot/Cold Flow, StateFlow for UI state 4. **ViewModel**: Unidirectional data flow, avoid Context 5. **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