Capas de la arquitectura de Jetpack Compose

En esta página se proporciona una descripción general de alto nivel de las capas de la arquitectura que componen Jetpack Compose, así como los principios básicos que conforman este diseño.

Jetpack Compose no es un proyecto monolítico único, sino que se crea a partir de varios módulos que se ensamblan para formar una pila completa. Si comprendes los diferentes módulos que componen Jetpack Compose podrás:

  • Usar el nivel adecuado de abstracción para compilar tu app o biblioteca
  • Comprender cuándo puedes "bajar" a un nivel inferior para tener más control o personalización
  • Minimizar tus dependencias

Capas

Las capas principales de Jetpack Compose son las siguientes:

Figura 1: Las capas principales de Jetpack Compose

Se compila cada capa sobre los niveles inferiores y se combinan las funcionalidades para crear componentes de nivel superior. Cada una de ellas toma como fundamento las API públicas de las capas inferiores para verificar los límites del módulo y permitirte reemplazar cualquiera de ellas si es necesario. Examinemos estas capas desde abajo hacia arriba.

Tiempo de ejecución
En este módulo se presentan los aspectos básicos del entorno de ejecución de Compose, por ejemplo, remember, mutableStateOf, la anotación @Composable y SideEffect. Puedes considerar compilar directamente en esta capa si solo necesitas las capacidades de administración de árbol de Compose, no su IU.
IU
La capa de la IU consta de varios módulos (ui-text, ui-graphics, ui-tooling, etc.). Estos módulos implementan los aspectos básicos del kit de herramientas de la IU, como LayoutNode, Modifier, controladores de entrada, diseños personalizados y dibujos. Considera compilar en esta capa si solo necesitas conceptos fundamentales de un kit de herramientas de la IU.
Base
En este módulo se proporcionan bloques de compilación agnóstica del sistema de diseño para la IU de Compose, como Row y Column, LazyColumn, el reconocimiento de gestos determinados, etc. Considera compilar sobre la capa base para crear tu propio sistema de diseño.
Material
En este módulo se proporciona una implementación del sistema Material Design para la IU de Compose y un sistema de temas, componentes de diseño, indicadores de ondas e íconos. Compila sobre esta capa cuando uses Material Design en tu app.

Principios de diseño

Un principio rector para Jetpack Compose es proporcionar pequeñas piezas enfocadas en la funcionalidad que se puedan ensamblar (o componer) en lugar de unos pocos componentes monolíticos. Este enfoque tiene varias ventajas.

Controla tus dispositivos

Los componentes de nivel superior tienden a hacer más cosas por ti, pero limitan la cantidad de control directo que tienes. Si necesitas más control, puedes "desplegar" para usar un componente de nivel inferior.

Por ejemplo, si deseas animar el color de un componente, puedes usar la API de animateColorAsState:

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

Sin embargo, si necesitas que el componente siempre comience en gris, no puedes hacerlo con esta API. En su lugar, puedes bajar de nivel para usar la API de Animatable de nivel inferior:

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

La API de animateColorAsState de nivel superior se compila a partir de la API de Animatable de nivel inferior. El uso de la API de nivel inferior es más complejo, pero ofrece más control. Elige el nivel de abstracción que mejor se adapte a tus necesidades.

Customization

Si ensamblas componentes de nivel superior con bloques de compilación más pequeños, es mucho más fácil personalizar los componentes en caso de que necesites hacerlo. Por ejemplo, considera la implementación de Button que proporciona la capa de Material:

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Un Button se ensambla a partir de 4 componentes:

  1. Un objeto Surface material que proporciona el fondo, la forma, el control de clics, etcétera

  2. Un objeto CompositionLocalProvider que cambia la versión alfa del contenido cuando el botón está habilitado o inhabilitado

  3. Un objeto ProvideTextStyle que establece el estilo de texto predeterminado que se usará

  4. Un objeto Row que proporciona la política de diseño predeterminada para el contenido del botón

Omitimos algunos parámetros y comentarios a fin de que la estructura sea más clara, pero el componente completo tiene solo unas 40 líneas de código, ya que simplemente ensambla estos 4 componentes para implementar el botón. Los componentes como Button se expresan sobre qué parámetros muestran, lo que crea un equilibrio entre la habilitación de personalizaciones comunes y una explosión de parámetros que pueden hacer que un componente sea más difícil de usar. Los componentes de Material, por ejemplo, ofrecen personalizaciones especificadas en el sistema de Material Design y, de esta forma, se simplifica seguir sus principios.

Sin embargo, si deseas realizar una personalización más allá de los parámetros de un componente, puedes "bajar" un nivel y bifurcar un componente. Por ejemplo, Material Design especifica que los botones deben tener un fondo de color sólido. Si necesitas un fondo con gradiente, esta opción no es compatible con los parámetros Button. En este caso, puedes usar la implementación Button de Material como referencia y compilar tu propio componente:

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

En la implementación anterior, se siguen usando componentes de la capa de Material, como los conceptos de la versión alfa del contenido actual y el estilo de texto actual de Material. Sin embargo, reemplaza el Surface de material por un Row y lo diseña para lograr el aspecto deseado.

Si no deseas usar conceptos de Material, por ejemplo, si compilas tu propio sistema de diseño a medida, puedes comenzar a usar solo componentes de la capa de base:

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

Jetpack Compose reserva los nombres más simples para los componentes de nivel superior. Por ejemplo, androidx.compose.material.Text se compila sobre androidx.compose.foundation.text.BasicText. Esto te permite proporcionar tu propia implementación con el nombre más detectable si deseas reemplazar niveles superiores.

Elige la abstracción correcta

La filosofía de Compose de crear componentes reutilizables en capas implica que no es necesario que busques siempre los bloques de compilación de niveles inferiores. Muchos componentes de nivel superior no solo ofrecen más funcionalidad, sino que también implementan prácticas recomendadas, como admitir la accesibilidad.

Por ejemplo, si deseas agregar compatibilidad con gestos a tu componente personalizado, puedes compilarlo desde cero mediante Modifier.pointerInput, pero hay otros componentes de nivel superior compilados sobre este, que pueden ofrecer un mejor punto de partida, por ejemplo, Modifier.draggable, Modifier.scrollable o Modifier.swipeable.

Como regla general, se recomienda que compiles sobre el componente de nivel más alto que ofrece la funcionalidad que necesitas para beneficiarte de las prácticas recomendadas incluidas.

Más información

Consulta la muestra de Jetsnack para ver un ejemplo de cómo compilar un sistema de diseño personalizado.