[CSA01] Hello @Compose!

tags: blog, compose, csa

Bienvenidos a Compose Series Android

Vamos a comenzar con esta serie de publicaciones para conocer, jugar y experimentar con Jetpack Compose, conocer las posibilidades que nos aporta en el desarrollo Android y cómo integrarlo en nuestras aplicaciones. Empezaremos como no con un Hola Mundo

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

¿Qué es Jetpack Compose?

Si aún no lo conocéis, Jetpack Compose es el nuevo framework de programación de interfaces de usuario recientemente anunciado por Google para el futuro del desarrollo de aplicaciones nativas Android. Este framework aprovecha todo el potencial del lenguaje Kotlin para cambiar el estándar de programación de interfaces de usuario proporcionando formas de trabajo más modernas, escalables, intuitivas y potentes:

  • Menos código: Sustituye los archivos de diseño por funciones compositivas que describen la interfaz de usuario y sus componentes.
  • Intuitivo: Compose sigue los principios de programación declarativa. El objetivo es describir la interfaz de usuario y que a medida que cambie el estado de la aplicación automáticamente actualizarla.
  • Desarrollo acelerado: Es compatible con el código existente, por lo que es posible adoptarlo en cualquier proyecto. Itera rápidamente con vistas previas en vivo sin tener que compilar el código fuente.
  • Potencia: Sigue las líneas de diseño de Material Design, soporte a tema oscuro, animaciones, etc.

Compose se integra perfectamente en la arquitectura propuesta por Google a través del conjunto de librerías Android Jetpack. Esta arquitectura se basa en el paradigma MVVM conjuntamente con el concepto de Unidirectional Data Flow (el flujo de datos siempre se realiza en la misma dirección) y Compose tiene intención de aportar un salto de calidad e innovación a la capa de presentación.

Cómo hemos comentado, Compose sustituye a la manera actual de implementar interfaces de usuario:

  • Eliminamos los archivos de diseño estáticos con extensión .xml que definían el estado inicial de una vista. Ahora todo se basa en funciones compositivas (marcadas con la anotación @Composable) en ficheros Kotlin con la que podemos jugar como si fueran piezas de Lego.
  • Ya no hay que mantener la relación entre los archivos de diseño y los controladores (Activities, Fragments, CustomViews) donde se les da lógica funcional, evitando posibles errores en tiempo de ejecución, reduciendo el tiempo de renderizado de las vistas al cargarlas.
  • Desaparecen la gestión con adaptadores para listas y páginas, con la gestión del reciclado de las vistas derivado de ellas, ya que la idea que subyace con Compose es de renderizar en la interfaz de usuario el estado actual del sistema. Si hay partes que cambian, estas se destruyen y se recrean siguiendo el concepto de recomposición inteligente.

Históricamente, la jerarquía de vistas de Android representa un árbol de elementos de interfaz de usuario. A medida que el estado de la aplicación cambia, se debe actualizar esta jerarquía para mostrar los datos, de manera manual de forma general. En los últimos años la industria comenzó a migrar a un modelo de interfaz de usuario declarativo con el objetivo de simplificar la ingeniería relacionada con la compilación y actualización de interfaces de usuario. Componse se fundamenta en el enfoque de evitar esta complejidad a la hora de actualizar los datos en la interfaz de usuario mediante composición/recomposición con widgets relativamente sin estado.

Hello @Compose !

Vamos a lo que nos gusta, empecemos a experimentar con el framework.

Configuremos el proyecto

Para trabajar con Compose, es necesario usar la versión Arctic Fox de Android Studio, aún en Beta: https://developer.android.com/studio/preview

Además, necesitaremos usar las siguientes tecnologías y dependencias:

  • Gradle 7.2.0
  • Kotlin 1.6.10
  • Compose 1.1.1
  • Activity-Compose 1.4.0
  • Material 1.6.1

Para facilitar la tarea, en Android Studio Arctic Fox, viene una plantilla de Compose con la que crearemos nuestro proyecto.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Configuramos el archivo build.gradle del proyecto para usar Gradle 7.0.3 y Kotlin 1.5.31

buildscript { ext{ gradle_version = '7.2.0' compose_version = '1.1.1' kotlin_version = '1.6.10' androidx_core_version = '1.8.0' androidx_appcompat_version = '1.4.2' google_material_version = '1.6.1' androidx_lifecycle_version = '2.4.1' androidx_activity_version = '1.4.0' } repositories { google() mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:$gradle_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } }

Habilitamos compose en el archivo build.gradle del módulo app, además de definir las dependencias que vamos a usar

android { // Enable Compose at app module buildFeatures { compose true } // Configure Compose composeOptions { kotlinCompilerExtensionVersion compose_version kotlinCompilerVersion kotlin_version } } dependencies { // AndroidX Core & AppCompat dependencies implementation "androidx.core:core-ktx:$androidx_core_version" implementation "androidx.appcompat:appcompat:$androidx_appcompat_version" // Material dependency implementation "com.google.android.material:material:$google_material_version" // Lifecycle dependency implementation "androidx.lifecycle:lifecycle-runtime-ktx:$androidx_lifecycle_version" // Compose dependencies implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling:$compose_version" // Activity-Compose integration dependency implementation "androidx.activity:activity-compose:$androidx_activity_version" }

Tu primera función componible

Compose se basa en funciones que se pueden componer. Estas funciones permiten definir la UI de forma pragmática describiendo su forma y dependencias de datos, en lugar de enfocarse en el proceso de construcción de la UI.

Para crear una función que se pueda componer, hay que agregar la anotación @Composable al nombre de la función.

El bloque setContent define el diseño de la actividad. En vez de definir el diseño con un archivo en formato XML, llamamos a funciones que se pueden componer. Este bloque está disponible gracias a que nuestra activity extiende de ComponentActivity de la dependencia Activity-Compose.

Compose usa un complemento de compilador de Kotlin para transformar estas funciones que se pueden componer en elementos de la aplicación. Por ejemplo, Text es una función definida en la biblioteca de UI de Compose y sirve para declarar un elemento de texto en nuestra aplicación.

class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Greeting("Android", color = Color.Green) } } } @Composable fun Greeting(name: String) { Text("Hello $name!") }

La vista preliminar es tu amiga

Puedes ejecutar la aplicación en un dispositivo o un emulador configurado, pero para ello tendrás que compilar el proyecto (y ya sabemos a veces lo que cuesta eso).

Para previsualizar los diseños realizados con Compose, al igual que existe la vista preeliminar para los XML clásicos, a partir de la versión Arctic Fox de Android Studio, el IDE trae una opción para mostrar la vista previa de una función componible. La principal restricción es que la función no debe recibir parámetros de entrada. Simplemente hemos de añadir la anotación @Preview a una función componible:

@Preview @Composable fun PreviewGreeting() { Greeting("Android") }

Hello Android Preview

Organización por favor

Los elementos de la UI son jerárquicos, ya que unos contienen a otros. En Compose, montas una jerarquía de UI llamando a funciones que se pueden componer desde otras funciones que también se puedan componer.

La función APentatonicMinorScale crea las 5 notas de la escala pentatónica menor de LA como elementos de texto, sin embargo, como no proporcionamos información alguna de la disposición, los elementos se dibujan unos encima de otros.

class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { APentatonicMinorScale() } } } @Composable fun APentatonicMinorScale() { Text("A") Text("C") Text("D") Text("E") Text("G") } @Preview @Composable fun DefaultPreview() { APentatonicMinorScale() }

APentatonicMinorScale without order

La función Column nos permite apilar los elementos de forma vertical.

La configuración predeterminada apila todos los elementos secundarios directamente, uno tras otro, sin espacios.
La columna misma se coloca en la esquina superior izquierda de la vista contenido.

@Composable fun APentatonicMinorScale() { Column { Text("A") Text("C") Text("D") Text("E") Text("G") } }

APentatonicMinorScale with column

Por último, agregamos estilo a la columna.

  • modifier: Permite configurar el diseño. Vamos a aplicar el elemento Modifier.padding

Para enriquecer el diseño y probar nuevas funciones de la biblioteca de Compose, incluimos una Image

  • height(180.dp): indicamos la altura de la imagen.
  • fillMaxWidth(): La imagen debe ser lo suficientemente ancha como para llenar el diseño al que pertenece.
  • contentScal = ContentScale.Crop: Especifica que el gráfico de la imagen debe llenar el ancho de la columna y recortar a la altura adecuada, de ser necesario.

Por último, vamos a añadir un nuevo elemento, Spacer para separar la imágen de la escala.

@Composable fun APentatonicMinorScale() { Column( modifier = Modifier.padding(16.dp), ) { Image( painter = painterResource(R.drawable.ic_launcher_foreground), contentDescription = null, modifier = Modifier .height(180.dp) .fillMaxWidth(), contentScale = ContentScale.Crop ) Spacer(Modifier.height(16.dp)) Text("A") Text("C") Text("D") Text("E") Text("G") } }

APentatonicMinorScale with column & modifiers

El toque Material

Compose está diseñado para admitir los principios de Material Design. Muchos de los elementos incluídos en la biblioteca lo implementan directamente. Aplicamos forma con uno de los pilares de Material Design como es Shape. Usaremos la función clip() para redondear las esquinas de la imagen. Shape es invisible, pero el gráfico se recorta para ajustarse a él.

Image( painter = painterResource(R.drawable.ic_launcher_foreground), contentDescription = null, modifier = Modifier .height(180.dp) .fillMaxWidth() clip(shape = RoundedCornerShape(4.dp)), contentScale = ContentScale.Crop )

Podemos también aplicar mejoras en el diseño de los textos aplicando fuentes, espaciado, etc

@Composable fun APentatonicMinorScale() { MaterialTheme { val typography = MaterialTheme.typography Column( modifier = Modifier.padding(16.dp), ) { Image( painter = painterResource(R.drawable.header), contentDescription = null, modifier = Modifier .height(180.dp) .fillMaxWidth(), contentScale = ContentScale.Crop ) Spacer(Modifier.height(16.dp)) Text("A Pente¡atonic Minor Scale is a classical Blues and Rock scale", style = typography.h6, maxLines = 2, overflow = TextOverflow.Ellipsis) Text("A", style = typography.body2) Text("C", style = typography.body2) Text("D", style = typography.body2) Text("E", style = typography.body2) Text("G", style = typography.body2) } } }

APentatonicMinorScale Complete

Conclusiones

Compose es una tecnología incipiente (a punto de salir la primera release, a lo largo del mes de Julio), y es la gran apuesta de renovación en la pila tecnológica de Android. La mayoría de widgets actuales se han migrado a Compose, pero otros no. Aún así Compose es tan flexible que nos permite de una forma muy sencilla definir cualquier elemento de interfaz (cómo iremos viendo durante la serie).

En resumidas cuentas, Jetpack Compose nace con el propósito de modernizar el desarrollo de interfaces de usuario, simplificar la gestión de estados y la manera de implementarlas e integrarlas con el resto de la arquitectura de la aplicación, permitiendo además construir diseños complejos inalcanzables en la actualidad.

Hemos aprendido a:

  • Conocer qué es Jetpack Compose
  • Configurar nuestro proyecto para hacer uso de Compose.
  • Cómo definir las funciones que admiten composición.
  • Usar y aplicar estilo a las columnas para dar orden al diseño.
  • Aplicar principios Material Design a funciones componibles.

Referencias