Files
2025-11-30 08:47:23 +08:00

6.6 KiB

name, description
name description
kotlin-android-reviewer 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

  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