Intro a Jetpack Compose

Oscar Presidente
6 min readOct 25, 2020

--

Jetpack Compose es el nuevo Toolkit Declarativo de UI para Android. Está diseñado con la eficiencia y facilidad de uso en mente.

El API declarativa de Jetpack Compose nos permite crear interfaces de forma rápida y, generalmente, utilizando menos código que sí lo hiciéramos utilizando un método imperativo convencional.

Modelo Imperativo

Normalmente en Android, representamos una interfaz gráfica como un árbol de View s, en el cual cada una las views que se muestran en pantalla es un nodo de dicho árbol.

Por lo general, hacer cambios en nuestro UI es un proceso de dos partes:

  1. Necesitamos obtener una referencia al objeto al que deseamos hacer cambios y lo más común es hacerlo usando findViewById para recorrer el árbol hasta encontrar el nodo que deseamos manipular.
  2. Utilizar el API del objeto que deseamos actualizar para cambiar sus propiedades. Por ejemplo, si quisiéramos actualizar la imagen mostrada por una ImageView podríamos utilizar setImage(Bitmap) cambiando el estado de nuestro widget.

Con el modelo Imperativo los widgets normalmente mantienen su propio estado interno y exponen APIs que le permite a la lógica de la aplicación mutar este estado a fin de obtener el UI que deseamos.

Modelo Imperativo

Modelo Declarativo

En los años recientes toda la industria ha estado poniendo más atención a un modelo declarativo con el objetivo de simplificar la ingeniería necesaria cuando diseñamos UI. La idea del modelo declarativo es que en lugar de cambiar el estado de objetos a los que mantenemos una referencia, los objetos son inmutables y nuestra UI es actualizada por medio de la reconstrucción del árbol de widgets.

Uno de los desafíos del modelo declarativo es que es potencialmente costoso en términos de tiempo, poder de computo y, en el contexto de los dispositivos móviles, batería. Por lo que los frameworks que adoptan este modelo deben implementar técnicas que mitiguen este costo, en el caso de Compose se hace a través de un proceso llamado Recomposición.

Modelo Declarativo

Jetpack Compose es una librería que nos permite implementar el estilo declarativo en nuestras aplicaciones de Android. Un ejemplo clásico es el Hello World; mientras que hacerlo de la forma tradicional no es algo trivial, pues supone la creación de los archivos tanto de código como layout, usando Compose es tan simple como invocar una función que emita el texto con el mensaje que deseamos:

Algunas cosas a tomar en cuenta de este pequeño ejemplo: 1. La función Greeting tiene la annotation @Composable. Esto le informa al compilador de Compose que la función pretende convertir data en UI. Todas las funciones que pretendan mostrar UI utilizando Jetpack Compose deben llevar está anotación. 2. La función recibe data. Las funciones anotadas con@Composable pueden aceptar parámetros de forma que la lógica de la aplicación pueda describir el UI. 3. La función no retorna nada. La función describe el UI que queremos obtener por lo que no es necesario que retorne ningún resultado. 4. No tiene ningún tipo de Side effects, de modo que llamarla en múltiples ocasiones producirá siempre el mismo UI.

En general, debemos mantener asegurarnos que las funciones @Composable que escribamos cumplan con estas propiedades.

A diferencia del approach Imperativo, en donde mutamos el estado de nuestros widgets en Compose nuestros widgets son relativamente stateless y, de hecho, ni siquiera son expuestos como objetos; el UI es actualizado llamando la función nuevamente con distintos parámetros.

Una cualidad de Jetpack Compose que vale la pena mencionar es la libertad y sofisticación que permite relativo a definir nuestros UI utilizando XML. Dado que para usamos Kotlin y no XML podemos, de forma simple y elegante, crear UI complejos utilizando unas cuantas líneas de código.

Hemos mencionado como los objetos en Jetpack Compose no mantienen un estado interno, a lo que se podría argumentar que está bien para apps simples, pero si quisiéramos hacer algo más interesante que mostrar algunos textos resultaría inviable; quizá queremos mostrar un widget que depende de algún evento como el input del usuario en un teclado, el acelerómetro, etc.

Normalmente en una aplicación de Android el flujo de UI luce así:

Flujo de actualización del UI

El flujo de eventos altera el estado de nuestros widgets, imaginemos un EditText cuyo text va cambiando a medida que el usuario teclea letras en un teclado virtual, el mismo evento de teclear altera el estado del widget, es decir, nuestros eventos y estado están sumamente asociados.

Por otro lado en Compose el estado y los eventos están separados. El estado representa un valor que puede cambiar, mientras que un evento representa una notificación que algo pasó:

Estados y eventos en Jetpack Compose

Necesitamos entonces utilizar los eventos para saber cuando debemos actualizar el estado de nuestra aplicación:

State en Compose

Podemos utilizar la función remember para crear una instancia de MutableState, de otra forma el valor será reinicializado en cada Composition. MutableState es un observable similar a MutableLiveData pero integrado en Jetpack Compose de forma que el proceso de Compose sepa cuando un valor es actualizado y pueda realizar el proceso de Recomposición en aquellas funciones @Composable que observan el valor.

Se puede crear un MutableState en una de tres formas:

Creando State en Compose

Ya hemos hablado de como los modelos imperativos requieren que los objetos mantengan su estado interno y expongan métodos que nos permitan transformarlo y como en Jetpack Compose, mutamos nuestro UI llamando las funciones con distintos parámetros, el hacerlo causa que los widgets emitidos por la función sean re-dibujados en pantalla, de ser necesario, con nueva data. El framework de Compose es capaz de recomponer solo los elementos que han cambiado.

Recomposición

Como se ha mencionado el proceso de recomponer todo el árbol de UI es potencialmente costoso en términos de poder de cómputo y batería. Por lo que para que el modelo declarativo sea viable se deben implementar estrategias que reduzcan estos costos.

Jetpack Compose lo hace a través de un proceso que ellos llaman Recomposición Inteligente. Cuando una porción del UI debe ser cambiado debido a cambios en los parámetros de una función, Compose es capaz de identificar las funciones y lambdas que pueden haber cambiado y omite el resto, de forma que solo el mínimo de funciones necesarias deban se llamadas nuevamente.

El framework intenta optimizar el modo en que las funciones @Composable son ejecutadas, por lo que hay ciertos puntos que vale la pena tener en cuenta cuando las escribes:

  1. Las funciones pueden ser ejecutadas en cualquier orden, por lo que es importante que nuestras funciones no dependan de side effects de otras @Composable.
  2. Las funciones pueden ser ejecutadas en paralelo.
  3. La Recomposición omite cuanto sea posible, por lo que nuestras funciones deberían ser auto-contenidas.
  4. La Recomposición puede ser cancelada.
  5. Las funciones @Composable pueden ser ejecutadas frecuentemente, en algunos escenarios tan frecuente como 1 vez por frame, por lo que es importante mantenerlas livianas y sin cálculos costoso o tardíos.

Conclusión

Hemos visto como Jetpack Compose puede ayudarnos a simplificar el desarrollo de nuestras aplicaciones en Android permitiendo crear UI elegantes y complejos con menos código y siguiendo un estilo que nos permite obtener resultados más predecibles siempre y cuando mantengamos en mente las propiedades que nuestras funciones deben poseer. En mi opinión Jetpack Compose es uno de esos proyectos que suponen una revolución en la forma de como trabajamos en determinada tecnología y aunque aún está en una etapa temprana de desarrollo podemos ver como avanza gracias al apoyo de la comunidad. Si deseas aprender más acerca de Jetpack Compose puedes encontrar más ejemplos e información en https://developer.android.com/jetpack/compose

--

--

Oscar Presidente

Android & Blockchain Engineer, amateur Body Builder and full time animal lover.