Cero a Uno con Flutter

Eduardo CQ
Comunidad Flutter
Published in
11 min readMar 20, 2019

--

Este artículo es una traducción al español y fue escrito originalmente por Mikkel Ravn y lo puedes encontrar en el siguiente enlace.

Fue a finales del verano 2016, y mi primera tarea como un nuevo empleado en la oficina Google en Aarhus, Dinamarca fue implementar gráficos animados en una aplicación Android/iOS usando Flutter y Dart. Ademas siendo un “NOOGLER”, fui nuevo en Flutter, nuevo en Dart, y nuevo en animaciones. De hecho nunca había hecho una aplicación móvil antes. Mi primer teléfono inteligente tenía apenas unos pocos meses de comprado en un ataque de pánico que podría fallar en la entrevista telefónica por responder la llamada con mi viejo Nokia…

Tuve algunas experiencias anteriores con gráficos desde el escritorio de Java, pero eso no estaba animado. Me sentí… raro. En parte un dinosaurio, en parte renacido.

TL;DR Descubriendo la fuerza del widget de Flutter y los conceptos de tween animaciones de gráficos en Dart para una aplicación Android/iOS.

Actualizado en Agosto 7, 2018 para usar la sintaxis Dart 2.
GitHub repositorio agregado en Octubre 17, 2018. Cada paso descrito abajo es un commit separado.

Pasar a una nueva pila de desarrollo te hace consciente de tus prioridades. Cerca de la parte superior de mi lista están estos tres:

  • Conceptos sólidos tratan la complejidad de manera efectiva al proporcionar formas simples y relevantes de estructurar pensamientos, lógica o datos.
  • Código claro nos permite expresar esos conceptos de manera limpia, sin distraernos con las fallas del idioma, la placa de caligrafía excesiva o los detalles auxiliares.
  • Iteración rápida es clave para la experimentación y el aprendizaje, y los equipos de desarrollo de software aprenden para ganarse la vida: cuáles son realmente los requisitos y cómo cumplirlos de la mejor manera con los conceptos expresados en código.

Flutter es una nueva plataforma para desarrollar aplicaciones Android/iOS desde un solo código base, escrito en Dart. Dado que nuestros requisitos hablaban de una IU bastante compleja que incluía gráficos animados, la idea de construirla solamente una vez parecía muy atractiva. Mis tareas incluían emplear herramientas CLI de Flutter, algunos widgets prediseñados, y su motor de renderización 2D — además de escribir un montón de código Dart simple para modelar y animar gráficos. A continuación te compartiré algunos aspectos conceptuales de mi experiencia de aprendizaje, y te proporcionaré un punto de partida para tu propia evaluación de la pila Flutter/Dart.

Un simple gráfico de barras animado, capturado desde un emulador iOS durante el desarrollo

Esta es la primera parte de una introducción de dos partes a Flutter y sus conceptos ‘widget’ y ‘tween’. Te ilustraré la fuerza de estos conceptos al usarlos para mostrar y animar gráficos como mostré arriba. Los ejemplos de código completo deben proporcionar una impresión del nivel de claridad de código que se puedes lograr con Dart. Incluiré suficientes detalles para que puedas seguirlos en tu propia portátil (y emulador o dispositivo), y experimentar la duración del ciclo de desarrollo Flutter.

El punto de comienzo es una instalación de Flutter nueva. Ejecuta:

$ flutter doctor

para comprobar la configuración:

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter
(Channel beta, v0.5.1, on Mac OS X 10.13.6 17G65, locale en-US)
[✓] Android toolchain - develop for Android devices
(Android SDK 28.0.0)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4)
[✓] Android Studio (version 3.1)
[✓] IntelliJ IDEA Community Edition (version 2018.2.1)
[✓] Connected devices (1 available)
• No issues found!

Con suficientes marcas de verificación, puedes crear una aplicación de Flutter. Vas a llamarla charts :

$ flutter create charts

Esto debe darte un directorio del mismo nombre:

charts
android
ios
lib
main.dart

Alrededor de sesenta archivos han sido generados, que forman una aplicación de ejemplo completa que la puedes instalar en ambos Android/iOS. Harás todo tu código en main.dart y archivos hermanos, sin necesidad presionar para tocar ninguno de los otros archivos o directorios.

Tu debes verificar que puedes lanzar la aplicación de ejemplo. Inicie un emulador o conecte un dispositivo, entonces ejecute

$ flutter run

en el directorio charts. Entonces deberías ver una simple aplicación de conteo en su emulador o dispositivo. Este usa widgets de Diseño de materiales, lo cual es bueno, pero opcional. Como la capa mas alta de la arquitectura de Flutter, esos widgets son completamente reemplazables.

Comienza por reemplazar el contenido de main.dart con el código de abajo, un simple punto de partida para jugar con animaciones de gráfico.

Guarde los cambios, luego reinicie la aplicación. Puedes hacerlo desde la terminal, presionando R . Esta operación de ‘reinicio completo’ desecha el estado de la aplicación, luego reconstruye la IU. Para situaciones donde el estado de la aplicación existente aún tiene sentido después de que el código cambia, puedes presionar r para hacer ‘hot reload , lo cual solamente reconstruye la IU. También hay un complemento Flutter para IntelliJ IDEA que proporciona la misma funcionalidad integrada con un editor de Dart:

Captura de pantalla desde IntelliJ IDEA con una versión vieja del complemento Flutter, mostrando los botones recarga y reinicio en la esquina superior derecha. Esos botones están habilitados, si la aplicación inició desde adentro del IDE. Versiones más nuevas del complemento hacen ‘hot reload’ al guardar.

Una vez reiniciada, la aplicación muestra una etiqueta de texto centrada diciendo Data set: null y un botón de acción flotante para actualizar los datos. Sí, humildes comienzos.

Para tener una idea de la diferencia entre hot reload y el reinicio completo, intenta lo siguiente: luego de presionar el botón de acción flotante varias veces, tome nota del número de conjunto de datos actual, luego reemplace Icons.refresh con Icons.add en el código, guardar y hacer un hot reload. Observe que el botón cambia, pero que el estado de la aplicación se mantiene; todavía estás en el mismo lugar en el flujo aleatorio de números. Ahora deshaga el cambio de icono, guardar y reinicie por completo. El estado de la aplicación se ha restablecido, y vuelves al Data set: null .

Tu simple aplicación muestra dos aspectos centrales del concepto de widget de Flutter en acción:

  • La interfaz de usuario esta definida por un árbol de widgets inmutables la cual se construye a través de un foxtrot de llamadas al constructor (donde puedes configurar widgets) y métodos build (donde las implementaciones de widgets pueden decidir cómo se ven sus subárboles). La estructura de árbol resultante para tu aplicación se muestra abajo, con el función principal de cada widget entre paréntesis. Como puedes ver, si bien el concepto widget es bastante amplio, cada tipo concreto de widget generalmente tiene una responsabilidad muy específica.
MaterialApp                    (navigation)
ChartPage (state management)
Scaffold (layout)
Center (layout)
Text (text)
FloatingActionButton (user interaction)
Icon (graphics)
  • Con un árbol inmutable de widgets inmutables que definen la interfaz de usuario, la única manera de que cambies esa interfaz es reconstruyendo el árbol. Flutter cuida de esto, cuando vence el siguiente fotograma. Todo lo que tienes que hacer es decirle a Flutter que ha cambiado algún estado del que depende un subárbol. La raíz de dicho subárbol dependiente del estado debe ser un StatefulWidget . Como cualquier widget decente, un StatefulWidget no es mutable, pero su subárbol esta construido por un objetoState. Flutter retiene los objetos deEstado en las reconstrucciones del árbol y adjunta cada uno a su respectivo widget en el nuevo árbol durante la construcción. Luego determinan cómo se construye el subárbol de ese widget. En tu aplicación, ChartPage es un StatefulWidget con ChartPageState como su State. Cuando el usuario presione el botón, ejecutarás código para cambiar ChartPageState. Haz demarcado el cambio con SetState para que Flutter pueda hacer su gestión interna y programar el árbol de widgets para su reconstrucción. Cuando eso suceda, ChartPageState construirá un subárbol ligeramente diferente enraizado en la nueva instancia de ChartPage.

Los widgets inmutables y subárboles dependientes del estado son las principales herramientas que Flutter pone a tu disposición para abordar las complejidades de la administración del estado en IUs elaboradas que responden a eventos asíncronos, tales como presionar botones, temporalizadores, o datos entrantes. Desde mi experiencia de escritorio diría que esta complejidad es muy real. Evaluando la fuerza del enfoque de Flutter es y debería ser un ejercicio para el lector: inténtalo en algo no trivial.

Tu aplicación de gráficos permanecerá simple en términos de estructura de widgets, pero harás un poco de gráficos personalizados animados. El primer paso es reemplazar la representación textual de cada conjunto de datos con un gráfico muy simple. Dado que un conjunto datos actualmente involucra solamente un solo número en el intervalo 0..100, el gráfico será un gráfico de barras con una sola barra, cuya altura está determinada por ese número. Usarás un valor inicial de 50 para evitar una alturanula :

CustomPaint es un widget que delega pintar a una estrategia CustomPainter.
La implementación de esa estrategia dibuja una sola barra.

El siguiente paso es agregar una animación. Cuando el conjunto de datos cambia, vas a querer que la barra cambie la altura suavemente en lugar de abruptamente. Flutter tiene un conceptoAnimationController para organizar animaciones, y al registrar un listener , se te dice cuándo cambia el valor de la animación, un doble que se va de cero a uno. Cuando eso suceda, tu puedes llamar a setState como antes y actualizar ChartPageState.

Por razones de demostración, tu primer intento será feo:

Ay. La complejidad ya asoma su fea cabeza, y tu conjunto de datos es aún un solo un número! El código necesario para configurar el control de animación es una preocupación menor, como esto no se ramifica cuando obtienes más datos del gráficos. El problema real son las variables startHeight, currentHeight y endHeight las cuales reflejan los cambios realizados al conjunto de datos y al valor de la animación, y se actualizan en tres lugares diferentes.

Estás necesitando de un concepto para lidiar con este lío.

Introducir tweens. Aunque está lejos de ser único para Flutter, son un concepto deliciosamente simple para estructurar código de animación. Su principal contribución es remplazar el enfoque imperativo anterior con uno funcional. Un tween es un valor. Este describe el ruta tomada entre dos puntos en un espacio de otros valores, como gráficos de barra, ya que el valor de animación se ejecuta de cero a uno.

Los tweens son genéricos en el tipo de esos otros valores, y pueden ser expresadas en Dart como objetos del tipo Tween<T>:

La jerga lerp proviene del campo de los gráficos de computadora y es corta tanto para la interpolación lineal (como un sustantivo) como para interpolar linealmente (como un verbo). El parámetro t es el valor de la animación, y un tween debería, por lo tanto, interrumpir desde el principio (cuando t es cero) hasta el final (cuando t es uno).

En el SDK de Flutter, la claseTween<T> es muy similar al de arriba, pero es una clase concreta que soporta la mutación inicio y final. No estoy totalmente seguro por qué fue hecha esa elección, pero probablemente haya buenas razones para ello en las áreas del soporte de animación del SDK que todavía tengo que explorar. En lo siguiente, usaras el Flutter Tween<T> , pero pretendo que es inmutable.

Tu puedes limpiar el código usando un solo Tween<double> para la altura de la barra:

Tu estás usando Tween para empaquetar los puntos finales de animación de altura de la barra en un solo valor. Se interconecta perfectamente con AnimationController y CustomPainter, evitando las reconstrucciones del árbol de widgets durante la animación ya que la infraestructura de Flutter ahora marca CustomPaint para volver a pintar en cada marca de animación, en lugar de marcar todo el subárbol de CustomPaint para reconstruir, rediseñar, y volver a pintar. Estas son mejoras definitivas. Pero hay mas en el concepto tween; ofrece una estructura para organizar tus pensamientos y código, y realmente no lo has tomado en serio. El concepto tween dice,

Animar Ts por trazar una ruta en el espacio de todos los Ts ya que el valor de animación se ejecuta de cero a uno. Modela la ruta con un Tween<T>.

En el código de arriba, T es un double, pero no quieres animar doubles, tu quieres animar gráficos de barras! Bueno, OK, solo barras por ahora, pero el concepto es fuerte, y se escala, si lo dejas.

(Quizás te preguntes ¿por qué no tomas ese argumento un paso más allá e insistes en animar conjuntos de datos en lugar de sus representaciones de gráficos de barras?. Esto es porque los conjuntos de datos, en contraste con gráficos de barras que son objetos gráficos, generalmente no habitan en espacios donde existen rutas fluidas. Los conjuntos de datos para los gráficos de barras tipicamente involucran datos numéricos mapeados contra categorías de datos discretos. Pero sin la representación espacial como gráficos de barras, no hay una noción razonable de una ruta fluida entre dos conjuntos de datos involucrando diferentes categorías.)

Volviendo al código, necesitaremos un tipo de Bar y una BarTween para animarlo. Extraiga las clases relacionadas con la barra en su propio archivo bar.dart al lado de main.dart:

Estoy siguiendo una convención del SDK de Flutter aquí al definir BarTween.lerp en términos de un método estático en la clase Bar. Esto funciona bien para tipos simples como Bar, Color, Rect y muchos otros, pero tendrás que reconsiderar el enfoque para los tipos de gráficos más complejos. No hay double.lerp en el SDK de Dart, por lo que estás usando la función lerpDouble del paquete dart:ui para el mismo efecto.

Tu aplicación ahora puede ser reexpresada en términos de barras como se ha mostrado en el código de abajo; he aprovechado la oportunidad de prescindir del campo dataSet.

La nueva versión es más larga, y el código adicional debería tener su peso. Lo hará, ya que abordaste el aumento de la complejidad del gráfico en la segunda parte. Tus requisitos hablan de barras coloreadas, barras múltiples, datos parciales, barras apiladas, barras agrupadas, barras apiladas y agrupadas, … todo animado. Manténgase sintonizado.

Un avance de una de las animaciones que harás en la parte dos.

Leer algunas de mis traducciones recientes de artículos sobre Flutter.

Si este artículo te ayudo en algo incluso en una o dos cosas, aplauda tantas veces como pueda para mostrar su apoyo. 👏

Soy Eduardo Coto. Técnico de computadoras y desarrollador de aplicaciones Flutter.
Puedes encontrarme en GitHub
Sígueme en Twitter.

--

--