Source profileQuality 67/100

affaan-m/ECC/skills/compose-multiplatform-patterns/SKILL.md

compose-multiplatform-patterns

Compose Multiplatform and Jetpack Compose patterns for KMP projects — state management, navigation, theming, performance, and platform-specific UI.

Source repository stars
234,327
Declared platforms
0
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Patterns for building shared UI across Android, iOS, Desktop, and Web using Compose Multiplatform and Jetpack Compose. Covers state management, navigation, theming, and performance.

Best for

    Not for

    • Using mutableStateOf in ViewModels when MutableStateFlow with collectAsStateWithLifecycle is safer for lifecycle
    • Passing NavController deep into composables — pass lambda callbacks instead

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/compose-multiplatform-patterns"
    Safe inspection promptEditorial

    Inspect the Agent Skill "compose-multiplatform-patterns" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/compose-multiplatform-patterns/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      When to Activate

      Building Compose UI (Jetpack Compose or Compose Multiplatform)

      Building Compose UI (Jetpack Compose or Compose Multiplatform)Managing UI state with ViewModels and Compose stateImplementing navigation in KMP or Android projects
    2. 02

      State Management

      Use a single data class for screen state. Expose it as StateFlow and collect in Compose:

      Use a single data class for screen state. Expose it as StateFlow and collect in Compose:For complex screens, use a sealed interface for events instead of multiple callback lambdas:
    3. 03

      ViewModel + Single State Object

      Use a single data class for screen state. Expose it as StateFlow and collect in Compose:

      Use a single data class for screen state. Expose it as StateFlow and collect in Compose:
    4. 04

      Collecting State in Compose

      Review the “Collecting State in Compose” section in the pinned source before continuing.

      Review and apply the “Collecting State in Compose” source section.
    5. 05

      Event Sink Pattern

      For complex screens, use a sealed interface for events instead of multiple callback lambdas:

      For complex screens, use a sealed interface for events instead of multiple callback lambdas:

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score67/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/compose-multiplatform-patterns/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Compose Multiplatform Patterns

    Patterns for building shared UI across Android, iOS, Desktop, and Web using Compose Multiplatform and Jetpack Compose. Covers state management, navigation, theming, and performance.

    When to Activate

    • Building Compose UI (Jetpack Compose or Compose Multiplatform)
    • Managing UI state with ViewModels and Compose state
    • Implementing navigation in KMP or Android projects
    • Designing reusable composables and design systems
    • Optimizing recomposition and rendering performance

    State Management

    ViewModel + Single State Object

    Use a single data class for screen state. Expose it as StateFlow and collect in Compose:

    data class ItemListState(
        val items: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val error: String? = null,
        val searchQuery: String = ""
    )
    
    class ItemListViewModel(
        private val getItems: GetItemsUseCase
    ) : ViewModel() {
        private val _state = MutableStateFlow(ItemListState())
        val state: StateFlow<ItemListState> = _state.asStateFlow()
    
        fun onSearch(query: String) {
            _state.update { it.copy(searchQuery = query) }
            loadItems(query)
        }
    
        private fun loadItems(query: String) {
            viewModelScope.launch {
                _state.update { it.copy(isLoading = true) }
                getItems(query).fold(
                    onSuccess = { items -> _state.update { it.copy(items = items, isLoading = false) } },
                    onFailure = { e -> _state.update { it.copy(error = e.message, isLoading = false) } }
                )
            }
        }
    }
    

    Collecting State in Compose

    @Composable
    fun ItemListScreen(viewModel: ItemListViewModel = koinViewModel()) {
        val state by viewModel.state.collectAsStateWithLifecycle()
    
        ItemListContent(
            state = state,
            onSearch = viewModel::onSearch
        )
    }
    
    @Composable
    private fun ItemListContent(
        state: ItemListState,
        onSearch: (String) -> Unit
    ) {
        // Stateless composable — easy to preview and test
    }
    

    Event Sink Pattern

    For complex screens, use a sealed interface for events instead of multiple callback lambdas:

    sealed interface ItemListEvent {
        data class Search(val query: String) : ItemListEvent
        data class Delete(val itemId: String) : ItemListEvent
        data object Refresh : ItemListEvent
    }
    
    // In ViewModel
    fun onEvent(event: ItemListEvent) {
        when (event) {
            is ItemListEvent.Search -> onSearch(event.query)
            is ItemListEvent.Delete -> deleteItem(event.itemId)
            is ItemListEvent.Refresh -> loadItems(_state.value.searchQuery)
        }
    }
    
    // In Composable — single lambda instead of many
    ItemListContent(
        state = state,
        onEvent = viewModel::onEvent
    )
    

    Navigation

    Type-Safe Navigation (Compose Navigation 2.8+)

    Define routes as @Serializable objects:

    @Serializable data object HomeRoute
    @Serializable data class DetailRoute(val id: String)
    @Serializable data object SettingsRoute
    
    @Composable
    fun AppNavHost(navController: NavHostController = rememberNavController()) {
        NavHost(navController, startDestination = HomeRoute) {
            composable<HomeRoute> {
                HomeScreen(onNavigateToDetail = { id -> navController.navigate(DetailRoute(id)) })
            }
            composable<DetailRoute> { backStackEntry ->
                val route = backStackEntry.toRoute<DetailRoute>()
                DetailScreen(id = route.id)
            }
            composable<SettingsRoute> { SettingsScreen() }
        }
    }
    

    Dialog and Bottom Sheet Navigation

    Use dialog() and overlay patterns instead of imperative show/hide:

    NavHost(navController, startDestination = HomeRoute) {
        composable<HomeRoute> { /* ... */ }
        dialog<ConfirmDeleteRoute> { backStackEntry ->
            val route = backStackEntry.toRoute<ConfirmDeleteRoute>()
            ConfirmDeleteDialog(
                itemId = route.itemId,
                onConfirm = { navController.popBackStack() },
                onDismiss = { navController.popBackStack() }
            )
        }
    }
    

    Composable Design

    Slot-Based APIs

    Design composables with slot parameters for flexibility:

    @Composable
    fun AppCard(
        modifier: Modifier = Modifier,
        header: @Composable () -> Unit = {},
        content: @Composable ColumnScope.() -> Unit,
        actions: @Composable RowScope.() -> Unit = {}
    ) {
        Card(modifier = modifier) {
            Column {
                header()
                Column(content = content)
                Row(horizontalArrangement = Arrangement.End, content = actions)
            }
        }
    }
    

    Modifier Ordering

    Modifier order matters — apply in this sequence:

    Text(
        text = "Hello",
        modifier = Modifier
            .padding(16.dp)          // 1. Layout (padding, size)
            .clip(RoundedCornerShape(8.dp))  // 2. Shape
            .background(Color.White) // 3. Drawing (background, border)
            .clickable { }           // 4. Interaction
    )
    

    KMP Platform-Specific UI

    expect/actual for Platform Composables

    // commonMain
    @Composable
    expect fun PlatformStatusBar(darkIcons: Boolean)
    
    // androidMain
    @Composable
    actual fun PlatformStatusBar(darkIcons: Boolean) {
        val systemUiController = rememberSystemUiController()
        SideEffect { systemUiController.setStatusBarColor(Color.Transparent, darkIcons) }
    }
    
    // iosMain
    @Composable
    actual fun PlatformStatusBar(darkIcons: Boolean) {
        // iOS handles this via UIKit interop or Info.plist
    }
    

    Performance

    Stable Types for Skippable Recomposition

    Mark classes as @Stable or @Immutable when all properties are stable:

    @Immutable
    data class ItemUiModel(
        val id: String,
        val title: String,
        val description: String,
        val progress: Float
    )
    

    Use key() and Lazy Lists Correctly

    LazyColumn {
        items(
            items = items,
            key = { it.id }  // Stable keys enable item reuse and animations
        ) { item ->
            ItemRow(item = item)
        }
    }
    

    Defer Reads with derivedStateOf

    val listState = rememberLazyListState()
    val showScrollToTop by remember {
        derivedStateOf { listState.firstVisibleItemIndex > 5 }
    }
    

    Avoid Allocations in Recomposition

    // BAD — new lambda and list every recomposition
    items.filter { it.isActive }.forEach { ActiveItem(it, onClick = { handle(it) }) }
    
    // GOOD — key each item so callbacks stay attached to the right row
    val activeItems = remember(items) { items.filter { it.isActive } }
    activeItems.forEach { item ->
        key(item.id) {
            ActiveItem(item, onClick = { handle(item) })
        }
    }
    

    Theming

    Material 3 Dynamic Theming

    @Composable
    fun AppTheme(
        darkTheme: Boolean = isSystemInDarkTheme(),
        dynamicColor: Boolean = true,
        content: @Composable () -> Unit
    ) {
        val colorScheme = when {
            dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
                if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
                else dynamicLightColorScheme(LocalContext.current)
            }
            darkTheme -> darkColorScheme()
            else -> lightColorScheme()
        }
    
        MaterialTheme(colorScheme = colorScheme, content = content)
    }
    

    Anti-Patterns to Avoid

    • Using mutableStateOf in ViewModels when MutableStateFlow with collectAsStateWithLifecycle is safer for lifecycle
    • Passing NavController deep into composables — pass lambda callbacks instead
    • Heavy computation inside @Composable functions — move to ViewModel or remember {}
    • Using LaunchedEffect(Unit) as a substitute for ViewModel init — it re-runs on configuration change in some setups
    • Creating new object instances in composable parameters — causes unnecessary recomposition

    References

    See skill: android-clean-architecture for module structure and layering. See skill: kotlin-coroutines-flows for coroutine and Flow patterns.

    Alternatives

    Compare before choosing

    Computed 62234,327

    affaan-m/ECC

    compose-multiplatform-patterns

    Use it for engineering and design tasks; the detail page covers purpose, installation, and practical steps.

    Computed 61234,327

    affaan-m/ECC

    compose-multiplatform-patterns

    Use it for engineering and design tasks; the detail page covers purpose, installation, and practical steps.

    Computed 10042,015

    coreyhaines31/marketingskills

    ab-testing

    When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program

    Computed 10042,015

    coreyhaines31/marketingskills

    churn-prevention

    When the user wants to reduce churn, build cancellation flows, set up save offers, recover failed payments, or implement retention strategies. Also use when the user mentions 'churn,' 'cancel flow,' 'offboarding,' 'save offer,' 'dunning,' 'failed payment recovery,' 'win-back,' 'retention,' 'exit survey,' 'pause subscription,' 'involuntary churn,' 'people keep canceling,' 'churn rate is too high,' 'how do I keep users,' or 'customers are leaving.' Use this whenever someone is losing subscribers o