Communitygithub.com

compose-multiplatform-patterns

Patrones de Compose Multiplatform y Jetpack Compose para proyectos KMP: gestión de estado, navegación, tematización, rendimiento y UI específica de plataforma.

¿Qué es compose-multiplatform-patterns?

Este skill proporciona una guía completa de patrones para construir interfaces de usuario compartidas con Compose Multiplatform (Android, iOS, Desktop y Web) y Jetpack Compose. Está diseñado para desarrolladores que trabajan en proyectos KMP y necesitan aplicar buenas prácticas en gestión de estado, navegación tipificada, diseño de composables reutilizables, optimización de rendimiento y tematización dinámica con Material 3. Incluye ejemplos concretos de código Kotlin para patrones como ViewModel con un único objeto de estado, recolección de estado con collectAsStateWithLifecycle, patrón de eventos para pantallas complejas, navegación tipo-safe con rutas @Serializable, APIs basadas en slots, orden correcto de modificadores, y uso de @Stable/@Immutable para evitar recomposiciones innecesarias. También cubre anti-patrones comunes y referencias a skills complementarios como android-clean-architecture y kotlin-coroutines-flows. Es útil tanto para nuevos proyectos como para migraciones de código existente, proporcionando bases sólidas para aplicaciones multiplataforma mantenibles y eficientes.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/compose-multiplatform-patterns

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Compose Multiplatform

Patrones para construir UI compartida en Android, iOS, Escritorio y Web usando Compose Multiplatform y Jetpack Compose. Cubre gestión de estado, navegación, tematización y rendimiento.

Cuándo activarlo

  • Al construir UI con Compose (Jetpack Compose o Compose Multiplatform)
  • Al gestionar estado de UI con ViewModels y estado de Compose
  • Al implementar navegación en proyectos KMP o Android
  • Al diseñar composables reutilizables y sistemas de diseño
  • Al optimizar la recomposición y el rendimiento de renderizado

Gestión de Estado

ViewModel + Objeto de Estado Único

Usa una sola clase de datos para el estado de la pantalla. Exponla como StateFlow y recógela en 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) } }
            )
        }
    }
}

Recolectando Estado en 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
) {
    // Composable sin estado — fácil de previsualizar y probar
}

Patrón de Sumidero de Eventos

Para pantallas complejas, usa una interfaz sellada para eventos en lugar de múltiples lambdas de callback:

sealed interface ItemListEvent {
    data class Search(val query: String) : ItemListEvent
    data class Delete(val itemId: String) : ItemListEvent
    data object Refresh : ItemListEvent
}

// En el 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)
    }
}

// En el Composable — una sola lambda en lugar de muchas
ItemListContent(
    state = state,
    onEvent = viewModel::onEvent
)

Navegación

Navegación con Tipos Seguros (Compose Navigation 2.8+)

Define las rutas como objetos @Serializable:

@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() }
    }
}

Navegación con Diálogos y Hojas Inferiores

Usa dialog() y patrones de superposición en lugar de mostrar/ocultar imperativo:

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

Diseño de Componibles

APIs Basadas en Slots

Diseña componibles con parámetros de slot para mayor flexibilidad:

@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)
        }
    }
}

Orden de los Modifiers

El orden de los modificadores importa — aplícalos en esta secuencia:

Text(
    text = "Hola",
    modifier = Modifier
        .padding(16.dp)          // 1. Diseño (padding, tamaño)
        .clip(RoundedCornerShape(8.dp))  // 2. Forma
        .background(Color.White) // 3. Dibujo (fondo, borde)
        .clickable { }           // 4. Interacción
)

UI Específica de Plataforma en KMP

expect/actual para Componibles de Plataforma

// 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 maneja esto mediante interoperabilidad UIKit o Info.plist
}

Rendimiento

Tipos Estables para Recomposición Omitible

Marca las clases como @Stable o @Immutable cuando todas sus propiedades sean estables:

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

Usa key() y Listas Perezosas Correctamente

LazyColumn {
    items(
        items = items,
        key = { it.id }  // Claves estables permiten reutilización de elementos y animaciones
    ) { item ->
        ItemRow(item = item)
    }
}

Diferir Lecturas con derivedStateOf

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

Evitar Asignaciones en Recomposición

// MAL — nueva lambda y lista en cada recomposición
items.filter { it.isActive }.forEach { ActiveItem(it, onClick = { handle(it) }) }

// BIEN — asigna cada elemento para que los callbacks permanezcan vinculados a la fila correcta
val activeItems = remember(items) { items.filter { it.isActive } }
activeItems.forEach { item ->
    key(item.id) {
        ActiveItem(item, onClick = { handle(item) })
    }
}

Tematización

Tematización Dinámica con Material 3

@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-patrones a Evitar

  • Usar mutableStateOf en ViewModels cuando MutableStateFlow con collectAsStateWithLifecycle es más seguro para el ciclo de vida
  • Pasar NavController profundamente en los componibles — en su lugar pasa lambdas de callback
  • Cómputo pesado dentro de funciones @Composable — muévelo al ViewModel o a remember {}
  • Usar LaunchedEffect(Unit) como sustituto de la inicialización del ViewModel — se re-ejecuta en cambios de configuración en algunos contextos
  • Crear nuevas instancias de objetos en parámetros de componibles — provoca recomposición innecesaria

Referencias

Consulta el skill: android-clean-architecture para estructura de módulos y capas. Consulta el skill: kotlin-coroutines-flows para patrones de corrutinas y Flows.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills relacionados