¿Cómo Flutter logra dibujar un widget en pantalla📱?🤔

Daniel Herrera Sánchez
Bancolombia Tech
Published in
7 min readJun 21, 2022

Parece una pregunta simple, pero hace parte de los conocimientos avanzados de Flutter y saber responderla de forma adecuada potenciará el rendimiento de tus soluciones y te ayudará a entender que no solo se debe a Skia, especialmente por la gran arquitectura interna que tiene. No siendo más, comencemos: 🧑🏽‍💻👩🏻‍💻.

Primero, repasemos los fundamentos

Comencemos nuestro recorrido entendiendo que existen tres arboles que se ejecutan en paralelo en Flutter 🤯. ¡No te sorprendas! Aunque en el pasado (a modo de chiste) hemos dicho que todo en Flutter es un widget, la realidad es que existen más elementos de fondo que nos ayudan a implementar nuestras soluciones.

La siguiente es un pregunta tipo examen: ¿Qué es un widget? Según la documentación, un widget es una descripción inmutable de una parte de la interfaz de usuario (enlace).

Déjame preguntarte, si los widgets son elementos inmutables (que no cambian) ¿tus aplicaciones en Flutter, entonces, son estáticas? 🤔

En Twitter hice esta pregunta para ver si alguien conocía la respuesta:

La mayoría de las personas respondieron que no sabían cómo explicarlo.

Como ves, el código base de Flutter en la definición de widget, tiene la anotación @inmutable lo que indica que, en efecto, estos elementos son estáticos. Entonces, ¿cómo se logra el dinamismo en Flutter? Para ello, debo hablarte acerca de los Element y el RenderObject.

Element

“Es una instancia de un widget en una ubicación particular en el árbol”(enlace). Esta definición, nos deja en claro que existe una instancia de un element por cada widget en el árbol; por lo tanto, no es difícil deducir que en nuestras aplicaciones, así como tenemos un árbol de widgets, tenemos uno de elementos. Estas instancias son mutables, es decir, pueden cambiar y comunicarse con el RenderObject..

RenderObject

Controla todos los tamaños, layouts y contiene toda la lógica necesaria para dibujar el widget al que esta asociado. Esa es la razón por la que el RenderObject es muy costoso de instanciar. Al igual que en el escenario anterior ,también podemos concluir que tenemos un árbol de RenderObject a la par de nuestro árbol de widgets y nuestro árbol de elementos.

Para unificar estos tres conceptos, he creado la siguiente ilustración:

Con estos conceptos podemos entender todo el ciclo de vida relacionado al dibujo de widgets en nuestras aplicaciones. A continuación, vamos a analizarlos🧐.

Mostrar un widget en pantalla, un viaje asombroso

Iniciemos con un ejemplo simple. Supongamos que estás construyendo una casa. Has pensado ¿qué tipo de personas necesitarías para hacerla? Por mencionar algunas, necesitarías un arquitecto para el diseño de los planos, un maestro de obra que los interprete y los obreros que lo materialicen.

Si alguna definición cambiara en los planos, ¿crees que sería necesario destruir todo lo que se ha construido? Lo más probable es que no. En la mayoría de casos, lo más lógico sería modificar la zona afectada. Siguiendo la línea de este ejemplo, te pregunto: ¿los planos físicos que tiene el maestro de obra son inmutables? La respuesta a esta pregunta es afirmativa, pues estos planos son los que tienen las ordenes que él comunicará a los obreros y la única alternativa que tiene de cambiarlos es recibiendo unos nuevos. De esta manera, el maestro de obra puede comparar los planos nuevos con los anteriores y tomar decisiones.

Con todo lo anterior, podemos comparar los widgets (la configuración), con los planos. Aquí, el maestro de obra es el Element y los obreros son el RenderObject. Así como en una gran obra necesitarás de muchos obreros, maestros de obra y planos, en Flutter también cuentas con estos elementos que son comúnmente conocidos como los tres árboles.

Ahora, con esta analogía en mente, veamos el paso a paso de una construcción de un elemento en pantalla de Flutter:

Para el primer ejemplo, voy a utilizar una de mis charlas favoritas de este tema que se dio en China en el 2019 (enlace). Allí, nos plantearon el siguiente escenario:

Cuando se ejecuta el método run App:

Lo primero que hace es adicionar este widget al árbol de widgets:

Luego, se crea el elemento que administrará el ciclo de vida y los estados del árbol de widgets:

Así, tendríamos nuestro árbol de widgets y nuestro árbol de elementos:

Posteriormente, el elemento crea el RenderObject :

En la siguiente sección se pasan todas las configuraciones definidas en nuestro widget al RenderObject:

No te sorprendas si algunos nombres son muy específicos. Así como en una obra de construcción existen trabajadores especializados, en Flutter contamos con widgets, elements y renderobjects enfocados en una solución. Ahora, tenemos lo siguiente:

Ya en esta instancia, el RenderParagraph es el que se encargará de pintar las configuraciones establecidas en el widget.

Ahora nos queda una inquietud: ¿por qué Flutter se tomó la molestia de utilizar tres árboles cuando todo este proceso lo podía contener en uno solo? Para entender esto tenemos que analizar lo que sucede ante un cambio. Veamos el segundo ejemplo:

En este escenario se esta ejecutando dos veces el método runApp, por lo tanto, en la primera ejecución se crearán los tres árboles que vimos, pero ¿qué pasa cuando se ejecuta la segunda instrucción?

Lo primero es que llegan unas nuevas configuraciones pues el widget ha cambiado. Ante esto se ejecuta el siguiente método canUpdate:

Este determina si puede existir un reúso y por lo tanto, no recrea el elemento. Aquí el elemento ejecuta la instrucción updateRenderObject:

Como puedes ver, es similar al create pero lo que hace es reutilizar el renderObject actualizando únicamente sus valores. Esto significa que tampoco se creo un nuevo renderObject.

Entonces, al final los únicos que cambiaron fueron los planos (las configuraciones) no sus ejecutores, lo que da una potencia de reúso impresionante.

Verifiquemos esto por nuestra cuenta

App- Ejemplo simple de cambio de imagen y texto

Si es cierto lo que hemos analizado, lo que esperamos es que se reutilice lo máximo posible. Pongamos a prueba lo aprendido con el siguiente ejercicio:

En un aplicación simple haremos que cuando el usuario presione el botón, cambie la imagen y aparezca un texto que nos indique la ruta en la cual está alojado el archivo. Como ves, las dos imágenes en la App tienen tamaños diferentes y textos diferentes (enlace al repo de la app).

Si es cierto lo que comentamos, el id del RenderObject seguirá siendo el mismo, tanto para el texto como para la imagen.

Por lo tanto, procedemos a ejecutar la aplicación y comparar la información obtenida (utilizando las herramientas de desarrollo de Flutter podemos ver el árbol de widgets y detalles como el id de los objetos):

RenderObject ID para imagen Dash Gamer (ID = RenderImage#of905).
RenderObject ID para imagen Dart Side (ID = RenderImage#of905).

Para la imagen se genera un RenderObject del tipo RenderImage. El RenderImage tiene un id #0f905, lo que significa que las dos imágenes han sido dibujadas por el mismo RenderObject 🤯. En otras palabras, sí se aplicó el reúso de elementos.

RenderObject id para el texto (RenderParagraph #2bea)

Para el caso del texto sucede lo mismo. Se reutiliza el RenderObject y pese a que sus valores han cambiado, el RenderParagraph tiene el mismo ID para los dos escenarios.

Ahora imagina esta capacidad de reciclado de objetos en nuestras aplicaciones, donde tenemos árboles de widgets y constantemente modificamos lo que vemos en pantalla. Esta es una de las razones por las cuales el performance de Flutter es excelente.

Además, nos permite evidenciar por qué es una buena práctica ser medidos con la reconstrucción de elementos y hacer buenos usos de nuestros Key, pues es la tarjeta de identificación de nuestros widgets.

Conclusión

Flutter renderiza los elementos en pantalla de la forma más óptima posible. Con este artículo conocimos los tres árboles que se ejecutan en nuestras aplicaciones y vimos un ejemplo del reúso de los objetos. Si este artículo te gustó, por favor dale muchas veces al icono de 👏. Si tienes alguna inquietud, déjanos saber en la caja de comentarios 💬.

--

--

Daniel Herrera Sánchez
Bancolombia Tech

Flutter,Dart@GoogleDevExpert💙 • 🔴Youtube Channel Weincode •👨🏻‍💻FlutterMedellin Community Lead • 🙅🏻‍♂️Angular Content creator • Speaker •GitHub: weincoder