Critical Android code review for Payoo Android app. Focuses on high-impact issues - naming conventions, memory leaks, UIState patterns, business logic placement, lifecycle management, and MVI/MVVM pattern violations. Use when reviewing Kotlin files, pull requests, or checking ViewModels, Activities, Fragments, UseCases, and Repositories.
Reviews Android Kotlin code for critical issues like memory leaks, lifecycle violations, and architecture problems. Activates when reviewing ViewModels, Activities, Fragments, or PRs to ensure Payoo app stability and maintainability.
/plugin marketplace add daispacy/py-claude-marketplace/plugin install py-plugin@py-claude-marketplaceThis skill is limited to using the following tools:
examples.mdstandards.mdExpert Android code reviewer for Payoo Android application, focusing on CRITICAL and HIGH PRIORITY issues that impact app stability, maintainability, and architecture.
Determine what to review:
Use Read tool to examine files, focusing on CRITICAL and HIGH PRIORITY issues only.
Impact: Code readability, maintainability, team collaboration
Check for:
PaymentViewModel, not pmtVM)paymentAmount, not payment_amount)MAX_RETRY_COUNT)is/has/should/can prefix (e.g., isLoading, not loading)isPaymentProcessing, not state1)user, not usr)Common violations:
// ❌ BAD
var usr: User? = null
val loading = false
var state1 = ""
// ✅ GOOD
var user: User? = null
val isLoading = false
var paymentState = ""
Impact: App crashes, ANR, poor performance
Check for:
Common violations:
// ❌ CRITICAL - Memory Leak
class PaymentViewModel : ViewModel() {
private var activity: Activity? = null // LEAK!
fun setActivity(act: Activity) {
activity = act
}
}
// ❌ CRITICAL - Coroutine not cancelled
GlobalScope.launch { // Will leak!
// work
}
// ✅ GOOD
class PaymentViewModel : ViewModel() {
// No Activity reference
fun doWork() {
viewModelScope.launch { // Cancelled when ViewModel cleared
// work
}
}
}
Impact: State consistency, UI reliability, debugging
Check for:
StateFlow<UIState> or State<UIState>Common violations:
// ❌ BAD - Scattered state
class PaymentViewModel : ViewModel() {
val isLoading = MutableStateFlow(false)
val errorMessage = MutableStateFlow<String?>(null)
val data = MutableStateFlow<Payment?>(null)
val isEmpty = MutableStateFlow(false)
}
// ✅ GOOD - Single UIState
sealed class PaymentUIState {
object Loading : PaymentUIState()
data class Success(val payment: Payment) : PaymentUIState()
data class Error(val message: String) : PaymentUIState()
object Empty : PaymentUIState()
}
class PaymentViewModel : ViewModel() {
private val _uiState = MutableStateFlow<PaymentUIState>(PaymentUIState.Loading)
val uiState: StateFlow<PaymentUIState> = _uiState.asStateFlow()
}
Impact: Testability, reusability, architecture integrity
Check for:
Common violations:
// ❌ BAD - Business logic in ViewModel
class PaymentViewModel(private val repository: PaymentRepository) : ViewModel() {
fun processPayment(amount: Double) {
viewModelScope.launch {
// ❌ Business logic in ViewModel!
if (amount <= 0) return@launch
val fee = amount * 0.02
val total = amount + fee
repository.savePayment(total)
}
}
}
// ✅ GOOD - Business logic in UseCase
class ProcessPaymentUseCase(private val repository: PaymentRepository) {
suspend operator fun invoke(amount: Double): Result<Payment> {
// ✅ Business logic here
if (amount <= 0) return Result.failure(Exception("Invalid amount"))
val fee = amount * 0.02
val total = amount + fee
return repository.savePayment(total)
}
}
class PaymentViewModel(private val processPaymentUseCase: ProcessPaymentUseCase) : ViewModel() {
fun processPayment(amount: Double) {
viewModelScope.launch {
// ✅ ViewModel only orchestrates
processPaymentUseCase(amount)
}
}
}
Impact: Crashes, memory leaks, state loss
Check for:
viewModelScope or lifecycleScope, NEVER GlobalScopeviewLifecycleOwner, NOT thisonCleared() (ViewModel) or onDestroy()repeatOnLifecycle or flowWithLifecycleCommon violations:
// ❌ CRITICAL - Wrong lifecycle owner in Fragment
class PaymentFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.uiState.observe(this) { // ❌ Should be viewLifecycleOwner
// Update UI
}
}
}
// ❌ CRITICAL - GlobalScope leak
GlobalScope.launch {
repository.getData()
}
// ✅ GOOD
class PaymentFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.uiState.observe(viewLifecycleOwner) { // ✅ Correct
// Update UI
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collect { state ->
// Handle state
}
}
}
}
Impact: Architecture consistency, maintainability, testability
MVVM Pattern Requirements:
MVI Pattern Requirements:
Check for:
Common violations:
// ❌ BAD - MVVM violation: ViewModel calling Repository directly
class PaymentViewModel(
private val paymentRepository: PaymentRepository // ❌ Should inject UseCase
) : ViewModel() {
fun loadPayments() {
viewModelScope.launch {
val payments = paymentRepository.getPayments() // ❌ Skip UseCase layer
}
}
}
// ❌ BAD - Exposed mutable state
class PaymentViewModel : ViewModel() {
val uiState = MutableStateFlow<UIState>(UIState.Loading) // ❌ Mutable exposed!
}
// ❌ BAD - Business logic in View
class PaymentActivity : AppCompatActivity() {
fun onPayClick() {
val amount = amountEditText.text.toString().toDouble()
if (amount > 1000) { // ❌ Business logic in Activity!
// apply discount
}
viewModel.processPayment(amount)
}
}
// ✅ GOOD - Proper MVVM
class PaymentViewModel(
private val getPaymentsUseCase: GetPaymentsUseCase, // ✅ UseCase injected
private val processPaymentUseCase: ProcessPaymentUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<PaymentUIState>(PaymentUIState.Loading)
val uiState: StateFlow<PaymentUIState> = _uiState.asStateFlow() // ✅ Immutable exposed
fun loadPayments() {
viewModelScope.launch {
_uiState.value = PaymentUIState.Loading
when (val result = getPaymentsUseCase()) { // ✅ Use UseCase
is Result.Success -> _uiState.value = PaymentUIState.Success(result.data)
is Result.Error -> _uiState.value = PaymentUIState.Error(result.message)
}
}
}
}
class PaymentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ✅ Only UI logic
lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is PaymentUIState.Loading -> showLoading()
is PaymentUIState.Success -> showPayments(state.payments)
is PaymentUIState.Error -> showError(state.message)
}
}
}
payButton.setOnClickListener {
viewModel.processPayment(amountEditText.text.toString()) // ✅ Just forward to ViewModel
}
}
}
Focus ONLY on CRITICAL (🔴) and HIGH (🟠) priority issues. Skip medium and low priority findings.
Provide structured output with:
🔴 CRITICAL - Fix immediately (blocks release)
🟠 HIGH PRIORITY - Fix before merge
# Android Code Review Report - Critical & High Priority Issues
## Summary
- 🔴 Critical: X issues (MUST fix before release)
- 🟠 High Priority: X issues (MUST fix before merge)
- ⏭️ Medium/Low issues: Skipped (not in scope)
## 🔴 CRITICAL ISSUES
### 🔴 Memory Leak - [Specific Issue]
**File**: `path/to/file.kt:line`
**Impact**: App crash, ANR, memory exhaustion
**Current**:
```kotlin
// problematic code
Fix:
// corrected code
Why: [Explanation of memory leak and crash risk]
File: path/to/file.kt:line
Impact: Resource leak, crash on configuration change
Current:
// problematic code
Fix:
// corrected code
Why: [Explanation]
File: path/to/file.kt:line
Impact: Code readability, team collaboration
Violations:
usr should be userloading should be isLoadingpmtVM should be paymentViewModelWhy: [Explanation]
File: path/to/file.kt:line
Impact: State inconsistency, hard to debug
Current:
// scattered state
Fix:
// sealed class UIState
Why: [Explanation]
File: path/to/file.kt:line
Impact: Not testable, hard to reuse, violates Clean Architecture
Current:
// business logic in ViewModel
Fix:
// business logic in UseCase
Why: [Explanation]
File: path/to/file.kt:line
Impact: Architecture inconsistency, hard to maintain
Current:
// ViewModel calling Repository directly
Fix:
// ViewModel calling UseCase
Why: [Explanation]
Before Release:
Before Merge:
[If applicable, acknowledge good patterns observed]
## Quick Reference
**Focus**: Only report CRITICAL and HIGH priority issues:
1. **Naming Conventions** - Abbreviations, wrong case, missing prefixes
2. **Memory Leaks** - Activity/Context/View references in ViewModel
3. **UIState Patterns** - Scattered state, exposed mutable state
4. **Business Logic Placement** - Logic in wrong layers
5. **Lifecycle Management** - GlobalScope, wrong lifecycle owner
6. **MVI/MVVM Violations** - Repository calls from ViewModel, business logic in View
**Skip**: Code style, documentation, performance (unless critical), security, tests, DI setup
## Tips
- **Focus on impact**: Only report issues that cause crashes, leaks, or violate core architecture
- **Be specific**: Reference exact line numbers and variable names
- **Show examples**: Always provide current vs. fixed code
- **Explain why**: Impact on stability, maintainability, testability
- **Be actionable**: Clear fix recommendations
- **No nitpicking**: Skip style issues handled by linter