Files
vnidrop/.codex/skills/compose-skill/references/anti-patterns.md
Hammed Abass fd4fff2c86 docs: add compose development guidelines and skills
Added a comprehensive collection of Compose development guidelines, best practices, and skill definitions in the .codex directory.

Co-authored-by: Junie <junie@jetbrains.com>
2026-07-07 16:55:43 +02:00

7.9 KiB

Anti-Patterns

Quick-reference table of cross-cutting patterns that hurt MVI Compose Multiplatform codebases. Domain-specific anti-patterns (navigation, networking, paging, DI, etc.) live in their respective reference files — see the "Detailed in" column.

For overengineering patterns (bloated base classes, unnecessary use cases, 4-type MVI), see clean-code.md.

Cross-Cutting Anti-Patterns

Anti-pattern Why it is harmful Better replacement Detailed in
Business logic inside composables forks source of truth, hurts testability, reruns during composition move logic into ViewModel/domain services architecture.md
Giant god-ViewModel blast radius too large, slow reasoning, hard ownership one ViewModel per screen or independent flow architecture.md
Scattered updateState/sendEffect with no structure state transitions hard to trace, mutations across callbacks disciplined onEvent() as single entry point clean-code.md
Unstable state models (mutable collections, lambdas in state) defeats Compose skipping, more recomposition immutable data classes, immutable collections performance.md
Duplicated derived data (total, formattedTotal, hasTotal all stored) bugs from drift, harder transitions keep canonical value + derive via computed property architecture.md
Broad state reads in parent composables recomposition cascades to all children slice state, pass only required props to each child performance.md
Mutable state passed deep into tree hidden writes, unpredictable data flow explicit props + callbacks compose-essentials.md
One-off events stored as consumable state (showSnackbarOnce = true) event replay on config change, stale effects separate Effect via Channel architecture.md
No-op state emissions (copy state when nothing changed) wasted recomposition cycles guard unchanged values before updating performance.md
Full-screen loading wipes existing content bad UX, layout jumps, lost user trust keep old content + inline refresh indicator ui-ux.md
ViewModel doing platform work directly (share, analytics, navigation) breaks testability, platform coupling emit effects, handle in Route composable architecture.md
Animation state in ViewModel for no reason (shakeCount, alpha) pollutes business state local composable animation state animations.md
Display strings stored too early (ViewModel emits pre-baked formatted text) locale inflexibility, state duplication, harder reuse keep canonical values until presentation boundary architecture.md
Poor lazy list keys (no key or index-based) state jumps between rows, broken animations stable key by domain ID lists-grids.md
Too many trivial composables (wrappers around single Text/Spacer) fragmentation, harder reading extract only meaningful boundaries clean-code.md
Platform abstraction too early (interfaces for everything before pain) unnecessary indirection, poor fit share business logic first, abstract real platform capabilities only cross-platform.md
Forcing MVI migration on existing codebase churn without value, team friction respect existing patterns, introduce MVI for new features only clean-code.md
Inline fully qualified package paths hurts readability, clutters business logic, hides intent behind package noise import at file top; use import ... as ... for name clashes clean-code.md

Examples

Business logic inside composables

// BAD — logic in composable; untestable, reruns on every recomposition
@Composable
fun CheckoutScreen(viewModel: CheckoutViewModel) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val total = state.items.sumOf { it.price * it.qty } // business logic here
    val tax = total * 0.08
    Text("Total: $${"$"}total  Tax: $${"$"}tax")
}

// GOOD — derive in ViewModel/state, composable only renders
data class CheckoutState(
    val items: List<LineItem> = emptyList(),
    val total: Double = 0.0,
    val tax: Double = 0.0,
)

@Composable
fun CheckoutScreen(state: CheckoutState, onEvent: (CheckoutEvent) -> Unit) {
    Text("Total: ${state.total}  Tax: ${state.tax}")
}

One-off events as consumable state booleans

// BAD — event replays on config change, race between read and reset
data class UiState(val showSnackbar: Boolean = false)

LaunchedEffect(state.showSnackbar) {
    if (state.showSnackbar) {
        snackbarHostState.showSnackbar("Saved")
        viewModel.onEvent(DismissSnackbar)   // consumer must remember to reset
    }
}

// GOOD — Channel delivers exactly once, survives config change
sealed interface Effect { data class ShowSnackbar(val msg: String) : Effect }

CollectEffect(viewModel.effects) { effect ->
    when (effect) {
        is Effect.ShowSnackbar -> snackbarHostState.showSnackbar(effect.msg)
    }
}

Domain-Specific Anti-Patterns

These reference files contain their own anti-pattern sections with detailed BAD/GOOD code examples:

Domain Reference What it covers
Architecture & MVI architecture.md event handling, state modeling, effect misuse, domain layer violations
Overengineering clean-code.md bloated base classes, 4-type MVI, use case wrappers, naming
Coroutines & Flow coroutines-flow.md GlobalScope, blocking dispatchers, unbound scopes, stateIn misuse
Performance performance.md recomposition, stability, state shape, read boundaries
Compose Essentials compose-essentials.md side effects, modifier ordering, CompositionLocal
Animations animations.md ViewModel animation state, graphicsLayer misuse, over-animating
Lists & Grids lists-grids.md keys, nested scrolling, contentType
UI/UX ui-ux.md disappearing content, layout jumps, loading states
Navigation (shared) navigation.md MVI navigation rules, anti-patterns for both Nav 2 and Nav 3
Paging paging-mvi-testing.md PagingData in UiState, key misuse, LoadState handling
Networking networking-ktor-testing.md MockEngine, DI integration, testing anti-patterns
Network Architecture networking-ktor-architecture.md plugin composition, error strategy, client lifecycle, result wrapper choice
Room Database room-database.md entity design, DAO patterns, migrations
DataStore datastore.md singleton enforcement, blocking reads, corruption
DI (Koin) koin.md module organization, scoping, ViewModel injection
DI (Hilt) hilt.md module structure, scoping, testing
Image Loading image-loading.md cache policy, transformations, placeholder usage
Testing testing.md missing ViewModel tests, mocking DI, testing internals
Cross-Platform cross-platform.md expect/actual misuse, premature abstraction
iOS Interop ios-swift-interop.md naming, nullability, Flow bridging
Resources resources.md Android R vs CMP Res, qualifier usage
Material Design material-design.md theme setup, component choice, adaptive layouts
Accessibility accessibility.md missing semantics, touch targets, contrast
Gradle & Build gradle-build.md hardcoded versions, buildSrc, convention plugin timing