Posted: 14 May 2021 5 Tiempo de lectura

Programación imperativa vs declarativa: Google Jetpack Compose

El paradigma de la programación declarativa ha vuelto a la actualidad de la mano de un entorno en el que este modelo siempre había estado ausente: las aplicaciones móviles.

En concreto -hay otros ejemplos como Flutter o SwiftUI-, vamos a hablar sobre el lanzamiento por parte de Google de Jetpack Compose, un kit de herramientas para compilar interfaces de usuario nativas para aplicaciones Android, haciendo uso del lenguaje Kotlin.

Pero antes de seguir, ¿qué es la programación declarativa? ¿qué diferencias hay respecto al otro gran paradigma, la programación imperativa? Aunque profundizar en estas cuestiones daría para un artículo muy extenso, vamos a intentar sintetizarlo.
Resumiendo mucho, la programación declarativa es aquella en la que podemos afirmar “di qué quieres, pero no cómo lo quieres”. Es decir, detallamos la solución a un problema sin definir cómo llegar a ella. Para lograrlo, se utilizan mecanismos internos de control a partir de la definición del problema en sí.

Respecto la programación imperativa, podemos declarar la solución a un problema mediante una serie de acciones a realizar. Si sintetizamos en una frase: “di qué quieres y cómo lo quieres”. Para ello hacemos uso de algoritmos y funciones que nos permitan cambiar el estado del programa con el fin de alcanzar la solución.

Para ilustrar estas diferencias pondremos un ejemplo mundano. El problema: una pareja llega a un restaurante y quiere sentarse a cenar:

-        Enfoque imperativo: “Hemos visto que hay una mesa libre para dos personas justo al lado de la barra y nos gustaría sentarnos a cenar”

-        Enfoque declarativo: “Mesa para dos, por favor”.

Como podemos observar, el enfoque declarativo posee un nivel de abstracción mucho mayor que el imperativo, otra de las características principales de este paradigma.

Teniendo medianamente claro qué es la programación declarativa, veamos cómo impactará este modelo de programación a la hora de desarrollar interfaces nativas para la plataforma Android.

Views vs Composables

Desde sus inicios, la composición de la interfaz de usuario en Android está basada en la clase View. El bloque básico del que heredan todos los componentes de interfaz de usuario. Un objeto View posee una forma rectangular y es responsable de su pintado y del manejo de eventos. A su vez es la clase base de ViewGroup, el elemento básico tipo contenedor (los conocidos Layouts) que se usa para albergar otros componentes y widgets (elementos de interfaz: botones, campos de texto, listados, etc.).

Para actualizar la interfaz de usuario en el modelo View, recorremos el árbol de vistas con funciones como findViewById() y así poder cambiar los nodos mediante llamadas a métodos como button.setText(String), container.addChild(View) o img.setImageBitmap(Bitmap). Esos métodos cambian el estado interno de la vista.

Hacer cambios de forma manual en una vista es una fuente de errores. Si un dato se procesa en varios lugares, podemos olvidar refrescar alguna de las vistas que lo muestran. También es posible crear estados ilegales, en los que dos actualizaciones entren en conflicto. Por ejemplo, una actualización podría intentar establecer un valor para un nodo que se acaba de quitar de la IU. Por lo general, la complejidad del mantenimiento de software aumenta con la cantidad de vistas que deben actualizarse.

En el enfoque declarativo de Compose, los widgets no exponen funciones para cambiar de estado. De hecho, los widgets no se exponen como objetos. Para actualizar la IU, se llama a la misma función que admite composición con diferentes argumentos. Eso facilita la asignación de estado a los patrones arquitectónicos. Luego, las funciones que admiten composición son responsables de transformar el estado actual de la aplicación en una IU cada vez que se actualizan los datos observables.

Código

Con Compose, ya no se usará el elemento clásico de interfaz “View”. La unidad básica se llama “Composable” y no es una clase, es una función:

@Composable

fun HelloText(){

               Text(“Hello”)

}

Una gran diferencia en Compose es que usa modificadores en vez de los atributos que poseen los elementos View:

TextView(Context context, AttributeSet attrs, int defStyleAttr)

Aparte del listado de atributos interno de la vista, hay otros como el ancho o el alto que son externos y necesitan código aparte para ajustarlos. Además, hay un alto acoplamiento con el objeto context.

En cambio, los modificadores de Compose son usados para decorar el elemento, ya sean características internas o externas.

Una gran ventaja de Compose es que la función Composable puede recibir cualquier tipo de parámetro, mientras que un elemento View usa un constructor con parámetros fijos.

En cuanto a los callbacks y listeners, mientras que en el modelo View hay que conectar los datos y las vistas de forma prácticamente manual, en Compose los atributos y sus cambios van en conjunción con una variable de estado:

val clickedState: MutableState<Boolean> = mutableStateOf(false)

 

    TextButton(

            onClick = {

                clickedState.value = ! clickedState.value

                onToggle(clickedState.value)

            })

    { Text(text = if (clickedState.value) "Clicked" else "Not clicked") }

Otra ventaja de Compose, que tiene un impacto explicado en la siguiente sección, es que no serán necesarios los ficheros xml de configuración de vistas. Con una simple llamada a la función Composable, toda la interfaz será creada según las especificaciones indicadas. Aún así, hay formas de hacer convivir los dos modelos, lo cual es una ventaja para poder ir migrando nuestras aplicaciones.

Métricas

Según algunos experimentos realizados por la comunidad de desarrolladores con migraciones de una app hacia Compose, se producen los siguientes cambios (aproximados, cada aplicación es un mundo) en las métricas:

Tamaño del apk: algo fundamental para el usuario final. Con Compose se estima que el tamaño total puede llegar a reducirse hasta un 40%.

Líneas de código xml: aunque el impacto en líneas de código Kotlin es casi imperceptible, la transición hacia Compose si difiere mucho en las de xml, dónde se pueden observar reducciones de hasta un 80%.

Número de métodos: se ha podido observar hasta un 20% de reducción de líneas de código.

Conclusión

Tomando en cuenta los beneficios que aporta Compose, la evolución del resto de tecnologías, y los cambios del paradigma en la programación, podemos asegurar que el futuro del desarrollo de interfaces de usuario en Android pasa por este nuevo framework de Google.

Aunque aún está en sus fases iniciales y requerirá un tiempo de adaptación de la comunidad, indudablemente será el estándar más pronto que tarde.

Autor del artículo

Francisco Carranza, senior delivery consultant de Consultoría Tecnológica