Por qué elegimos Jetpack Compose. Ilustrado con ejemplos funcionales | de Chen Zhang | agosto 2022

Ilustrado con ejemplos funcionales.

Hace poco escribí (y actualicé) una publicación de blog sobre la función de nuestro producto que se reescribió por completo con Jetpack Compose. Puedo resumir tres razones por las que lanzamos Compose y creo que es el futuro para el desarrollo de la interfaz de usuario de Android. También quiero incorporar la teoría en nuestro trabajo de funciones y comparar el código resultante de Compose con el antiguo sistema View. Muchos ingenieros de Android comenzaron a aprender Compose a partir del artículo Thinking in Compose del sitio oficial para desarrolladores. De hecho, la introducción capturó dos razones clave por las que el equipo de Android de Google desarrolló el nuevo conjunto de herramientas de interfaz de usuario desde una perspectiva de plataforma. Además, como desarrollador de aplicaciones, también me gustaría mencionar la tercera razón por la que me encanta usar Compose desde la perspectiva de los usuarios de API. Comencemos con el extracto de Thinking in Compose. Ofreceré mi interpretación y la ilustraré con una comparación de códigos.

En el pasado, una jerarquía de vistas de Android se podía representar como un árbol de widgets de interfaz de usuario. Cuando el estado de la aplicación cambia debido a cosas como la interacción del usuario, la jerarquía de la interfaz de usuario debe actualizarse para mostrar los datos actuales. La forma más común de actualizar la interfaz de usuario es recorrer el árbol usando funciones como findViewById() y agregar nodos llamando a métodos como button.setText(String), container.addChild(View) o img.setImageBitmap(Bitmap). . Estos métodos cambian el estado interno del widget. En los últimos años, toda la industria ha comenzado a pasar a un modelo de interfaz de usuario declarativo, que simplifica enormemente el diseño asociado con la creación y actualización de interfaces de usuario. La técnica funciona reconstruyendo conceptualmente toda la pantalla desde cero y luego aplicando solo los cambios necesarios. Este enfoque evita la complejidad de actualizar manualmente una jerarquía de vistas con estado. Compose es un marco de interfaz de usuario declarativo.

Declarativo es un eslogan y puede significar diferentes cosas en diferentes contextos. Sin embargo, tendría más sentido si comparáramos los estilos de programación declarativos con los imperativos en los ejemplos. Entonces podemos ver sus diferencias relativamente. En el antiguo sistema Android View, escribimos la interfaz de usuario inflando los widgets de View y luego manipulamos sus estados internos llamando a getters y setters. Llamemos a este paradigma obligatorio porque los desarrolladores de aplicaciones necesitan controlar manualmente el estado interno de los widgets de vista. Sin embargo, en Jetpack Compose, los desarrolladores de aplicaciones ya no tienen acceso directo a los objetos de los widgets. La jerarquía de la interfaz de usuario subyacente está oculta detrás de la API Compose declarativa. La razón por la que se llaman declarativos es porque la forma en que llamamos a estas API de función se lee como si estuviéramos describiendo cómo se ve y se comporta la interfaz de usuario. Por lo tanto, la codificación imperativa es más sobre cómo y la declaración es más sobre qué. Porque la biblioteca de redacción solo expone la API de DSL y encapsula gran parte del trabajo pesado subyacente. Usemos un ejemplo de trabajo para comparar los resultados: queremos crear una lista vertical de habitaciones como se muestra a continuación:En el sistema de vista, generalmente definimos recycler_view.xml, row_item.xml, RecyclerViewAdapter y el adaptador de enlace y configuración. Nota: la separación forzada de XML y el código lógico (mencionada en el motivo n.° 3) requeriría el inflado manual de la vista y el enlace de la vista (vista del reciclador y vista de elementos de línea) en el código lógico. Además, los desarrolladores de aplicaciones deben administrar manualmente el enlace de datos y configurar el diseño. Como resultado, todo suma. Con Compose, escribir código en la API declarativa daría como resultado lo siguiente: Describimos la interfaz de usuario de la siguiente manera:

  • Cree una columna con elementos (como datos) y lazyListState (como estado del widget)
  • Ensamble cada elemento en RoomItem

La cantidad de código habla por sí sola de la eficiencia. En el sistema de visualización, la aplicación debe contener el estado de la aplicación en cada pantalla, los widgets de visualización también deben contener sus estados internos. Pero en Jetpack Compose, los desarrolladores de aplicaciones no tienen referencias a los objetos de vista y no cambian manualmente sus estados internos. En su lugar, podemos simplemente crear funciones componibles como esta:@Composable fun FunctionName(estado de entrada: T) { …}Tenga en cuenta que anotamos la función con @Composable y la función no tiene tipo de retorno. Al hacer esto, le decimos al compilador de composición que esta función debe transformar el estado de entrada en un nodo que se registra en el árbol de composición. El árbol de composición es la representación en memoria de las vistas de la interfaz de usuario que administra Compose-runtime. Las funciones componibles serían emitir cambios planificados en los nodos del árbol de la interfaz de usuario. El modelo mental se puede representar de la siguiente manera:Composable function La magia subyacente fue realizada por el compilador compose, que agrega un parámetro implícito, composer, a la función componible para realizar gran parte del trabajo subyacente, como el seguimiento, el almacenamiento en caché y la optimización. Esto es similar a agregar el parámetro de continuación implícito para suspender funciones en rutinas. La naturaleza de la arquitectura elimina la necesidad de que los desarrolladores de aplicaciones administren los estados internos de los widgets de vista. En cambio, solo la entrada de estado determina cómo se representa la interfaz de usuario. La nueva arquitectura trae las siguientes ventajas:

  • Al eliminar la gestión de widgets de indicadores de estado internos, realmente proporcionó el flujo de datos unidireccional: InputState => UI
  • Los desarrolladores de aplicaciones solo tienen que describir cómo debería ser el estado actual y ya no tienen que preocuparse por el estado anterior de la interfaz de usuario y la transición de un estado a otro. La biblioteca se encargaría de eso.
  • Esto permite que la gran mayoría de los ajustes de rendimiento se realicen en el nivel de la biblioteca Compose, lo que facilita esta tarea abrumadora para los desarrolladores de aplicaciones.

Usemos otro ejemplo para ilustrar cómo se desarrolla la nueva arquitectura en el mundo real. La característica es que, asumiendo que ya teníamos una fila de habitaciones en el estado visible del andamio y una columna de habitaciones en el estado boca abajo, queremos la misma posición desplazada de fila a columna y viceversa. Como se muestra abajo. IOW, la posición desplazada se sincroniza entre fila y columna.Sincronización de la posición de desplazamiento Durante la transición, queremos que tanto la fila como la columna se representen en la pantalla con diferente transparencia aumentada/disminuida gradualmente:Con el sistema View, un esqueleto de código típico se vería así:

  • Inflar HorizontalRecyclerView
  • Inflar VerticalRecyclerView
  • durante la transición Obtener y pasar la posición de desplazamiento manualmente de una orientación de la lista a la otra

Debido al diseño del sistema de visualización, no solo tenemos que inflar y pellizcar las vistas del reciclador, sino que también debemos administrar manualmente los estados internos de la vista del reciclador: las posiciones desplazadas. En Compose codificaríamos algo como esto: además de la brevedad del código (mencionado en la razón n.º 1), la arquitectura de la unidad de estado desempeñó un papel importante en la diferenciación del código resultante. El estado de entrada para la función componible tiene dos cosas:

  • artículos: lista
  • estado de lista: LazyListState

Se los dimos a LazyRow y LazyColumn. Tenga en cuenta que incluso pasamos esos exactamente la misma instancia de lazyListState tanto en LazyRow como en LazyColumn. De hecho, le estamos diciendo a Compose-runtime que represente Fila y Columna en función del mismo estado de desplazamiento. Así de fácil logramos la sincronización de las posiciones de scroll. Tampoco tiene que preocuparse por la posición de desplazamiento anterior y la transición, ya que Compose representa la interfaz de usuario en función del estado de entrada actual para cada cuadro. Gracias a la arquitectura con estado de Compose y al flujo de datos unidireccional, se puede lograr fácilmente una funcionalidad como esta. Pensar en Redactar puede no apuntar específicamente a esto. Pero personalmente, esa fue en realidad la razón principal por la que me involucré con Compose en primer lugar. Gracias al diseño del sistema Android View, el desarrollo de la interfaz de usuario basado en XML se convierte en una base de conocimiento separada del desarrollo de software central. Tuvimos que aprender a usar XML para crear diseños, atributos, estilos, temas, animaciones, etc. Programación funcional, rutinas, principios de ingeniería de software para escribir código legible y reutilizable. Las funciones componibles son similares a las funciones normales de Kotlin. Podemos expresar condicionales y bucles con las mismas aspiraciones de codificación a las que estamos acostumbrados. Cuanto más sabemos sobre la API de Compose y su implementación interna, más familiarizados la encontramos con nuestra base de conocimientos central:

  • DSL captura muchos aspectos de la programación funcional, como extensión, funciones de orden superior, lambda con receptores, operador y funciones infijas (por ejemplo, implementaciones).
  • Características de Kotlin como inmutabilidad, argumento lambda final, parámetros de función con nombre y valores predeterminados, delegados (p. ej., por), desestructuración (p. ej., mutableStateOf), clases en línea (p. ej., color), singleton (p. ej., diseño), fábrica (p. ej., LazyListState)
  • Programación asíncrona con rutinas de Kotlin para animaciones de interfaz de usuario
  • Patrones como programación reactiva como estado observable y flujo

Hice las 3 razones por las que nosotros (también nosotros 😅) adoptó Jetpack Compose. Sin embargo, hubo inconvenientes que experimentamos durante el viaje:

  • Incompatibilidad y errores en Compose y su pila tecnológica (Kotlin, Gradle, Android Studio y otras bibliotecas)
  • Funciones faltantes para alinear completamente el sistema View

Pero estos son lo que yo llamo «dolores de crecimiento», obstáculos y obstáculos inevitables en el camino de la luz. Ya debería haber muchos recursos para comprender Kotlin y DSL. Pero con respecto a la arquitectura y las partes internas de Compose (compilador/tiempo de ejecución/UI), parecía haber información perspicaz limitada disponible en línea. La advertencia también es que algunos de ellos pueden contener conjeturas de los autores. Sin embargo, he adjuntado algunos recursos aquí, ya que me fueron útiles para comprender mejor Compose. Espero que sean útiles.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.