Compose Architecture Review
When to Use This Skill: - Reviewing EXISTING Compose screens/ViewModels - Code review for architectural violations - Refactoring guidance for specific files - Validating state management patterns When to Use project-architecture-analyst-android Agent Instead: - Planning NEW features (uncertain file placement) - Understanding project-wide architecture - Major changes affecting multiple modules - Need to discover existing patterns before implementingReview Checklist
Separation of Concerns
- Composables: presentation only (no business logic, network, DB)
- ViewModels: state + business logic (StateFlow, events)
- Repository/UseCase: data access separated from UI
State Management
remember- composable-local state onlyrememberSaveable- survive config changesViewModelwithStateFlow/MutableStateFlow- screen statecollectAsStateWithLifecycle()- lifecycle-aware collectionderivedStateOf- computed state from other state- CompositionLocal - shared read-only state
- Use unidirectional data flow: state flows down, events flow up
- Prefer stateless composables (receive value + onChange callbacks)
- Use plain state-holder classes for complex state not tied to lifecycle
- Prefer side effects via
LaunchedEffect(async work),DisposableEffect(cleanup),SideEffect(framework integration),produceState,rememberCoroutineScope, orsnapshotFlowrather than arbitrary side effects in composable bodies - Use
SavedStateHandlefor persistent state across process death
State Lifecycle Issues
- No leaked ViewModels (use viewModel() or hiltViewModel())
- Proper cleanup in ViewModel.onCleared()
- StateFlow collected with lifecycle awareness
- No unnecessary state hoisting
Dependency Injection
- Dependencies injected (check project: Koin, Hilt, Dagger, or manual)
- No hardcoded singletons in composables
- ViewModels receive dependencies via constructor
Composable Design
- Composables are small (single responsibility)
- Complex screens broken into components
- Reusable components extracted
- Stateless composables preferred (state hoisting)
Common Issues
| Issue | Fix |
|---|---|
| Business logic in composable | Extract to ViewModel |
| Network call in composable | Move to ViewModel with LaunchedEffect |
| Singleton in composable | Inject via DI or CompositionLocal |
| 200+ line composable | Break into smaller components |
| State not hoisted | Hoist to parent or ViewModel |
| Side effects or network calls directly in composable | Wrap in LaunchedEffect(key) {...} or move to ViewModel |
| Stateful composable when stateless would suffice | Hoist state and pass value + lambda |
State Hoisting Pattern
Composable receives: value: T, onValueChange: (T) -> Unit ViewModel holds: MutableStateFlow<T>
Severity
- 🔴 Critical: Architecture violation
- 🟡 Improvement: Better pattern available
- 🟢 Enhancement: Optional optimization