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.
/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
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.