latest

Conociendo Flutter. Descripción técnica

Vamos a usar el documento de especificación técnica de Flutter para tratar de explicar adecuadamente qué es Flutter y cuáles son los pilares fundamentales que permiten su funcionamiento.

¿Qué es Flutter?

Flutter es un SDK de aplicaciones móviles que permite crear aplicaciones de alto rendimiento y alto grado de fidelidad para iOS y Android, a partir de una única base de código (proyecto único). Dicho en otras palabras, permite a los desarrolladores ofrecer aplicaciones que funcionan de manera fluida y se sienten naturales en diferentes plataformas. Teniendo en cuenta las diferencias en los comportamientos de desplazamiento, la tipografía, los iconos, etc.

Lo que se muestra arriba son pantallazos de una aplicación de demostración (Shrine) de la Galería de Flutter, una colección de aplicaciones de ejemplo de que podéis ejecutar después de instalar Flutter y configurar vuestro entorno. Shrine tiene imágenes de de alta calidad que permiten scrolling, presentación de artículos en forma de "tarjetas interactivas" (interactive cards), botones, listas desplegables y una página de "carrito de compras". Para ver la base de código único de éste y otros ejemplos, visitad el repositorio GitHub de Flutter.

No se requiere apenas experiencia en desarrollo móvil para empezar a utilizar Flutter. Las aplicaciones están escritas en Dart, que, como habéis podido comprobar, parece bastante familiar si has utilizado un lenguaje como Java o C#.

¿Por qué usar Flutter?

Según Google, éstas son algunas de las ventajas que conlleva usar Flutter, ayudándo a :

  • Ser altamente productivo.
  • Desarrollar para Android e iOS desde una única base de código.
  • Desarrollar ágilmente y de forma eficiente dado que permite hacer más con menos código de un lenguaje moderno, expresivo y con un enfoque declarativo.
  • Prototipar con facilidad y de manera iterativa:
    • Podréis experimentar modificando el código y recargando a medida que vuestra aplicación se ejecuta (función "hot reload").
    • Corregir los fallos y continuar la depuración desde el punto en el que la aplicación se colgó.
  • Crear experiencias de usuario agradables estéticamente y altamente personalizadas:
    • Beneficiándose de un conjunto extenso de widgets de Material Design y Cupertino (iOS-flavor) construidos usando el propio framework de Flutter.
    • Realizar elaborados diseños y componentes de la Interfaz de Usuario personalizados, más allá de las limitaciones de los conjuntos de widgets prestablecidos (OEM) por la plataformas móviles.

Principios funcionales de Flutter

En Flutter todo es un widget. Los widgets son los elementos básicos de la interfaz de usuario de una aplicación Flutter. Cada widget es una declaración inmutable de parte de la interfaz de usuario. A diferencia de otros frameworks que separan las vistas (interfaces de usuario), de los controladores de vistas (lógica), los layouts y otras propiedades, Flutter tiene un consistente y unificado patrón / modelo de diseño basado en un objeto: el widget.

Un widget puede definir:

  • Un elemento estructural (como un botón o un menú).
  • Un elemento de estilo o estético (como un tipo de letra / fuente o un esquema de colores ).
  • Un aspecto del layout (que define la disposición de otros widgets en la pantalla o propiedades relacionadas como el margin y el padding).
  • ...

Los widgets forman una jerarquía basada en composición. Cada widget anida otros en su interior que lo componen y además heredan propiedades del primero (esto lo entenderemos perfectamente con los primeros ejemplos de aplicaciones en Flutter). No existe un objeto "aplicación" separado. En su lugar, el widget raíz asume esta función.

Como respuesta a eventos provocados por las interacciones del usuario se puede solicitar al framework que reemplace un widget en la estructura jerárquica por otro. El framework compara los widgets nuevos y antiguos y actualiza eficientemente la interfaz de usuario.

Composición > herencia

Como venimos comentando, los widgets se componen a menudo de otros widgets más pequeños con propósitos específicos que se combinan para producir efectos poderosos. Por ejemplo, el widget de uso común Container está compuesto por varios widgets responsables de aspectos como el diseño, el esquema de color, el posicionamiento y el layout, así como la definición de su tamaño.

Específicamente, Container se compone de los widgets LimitedBox, ConstrainedBox, Align, Padding, DecoratedBox y Transform. Además, más allá de heredar de un Container para crear un contenedor personalizado, se puede combinar éste, junto con otros widgets sencillos para crear elementos completamente novedosos para la interfaz de usuario.

La jerarquía de widgets trata de ser superficial (crecer en horizontal) para maximizar el número de posibles combinaciones de widgets para componer otros más complejos.

Por ejemplo, un modo de proceder para centrar un widget, es envolverlo por un widget Center. Tenemos widgets para ajustar el padding, controlar el alineamiento, la disposición en filas, columnas y grids; este tipo de widgets centrados en el estilo o el manejo del layouts no tienen una representación visual propia. Su único propósito es controlar algún aspecto del diseño de otro widget vecino o anidado. Así pues, para entender por qué un widget se renderiza de cierta forma, a menudo es útil inspeccionar los widgets vecinos y en niveles de jerarquía superior.

Disposición en capas

El framework de Flutter está organizado en una serie de capas, cada una de las cuales se construye sobre la capa anterior. El diagrama siguiente ilustra la organización en capas.

Las capas superiores de la estructura son utilizadas con más frecuencia que las capas inferiores. Para ver el conjunto completo de librerías que componen el framework por capas de Flutter, os animamos a consultar la documentación de la API.

El diseño en capa ayuda a "hacer más con menos código". Por ejemplo, la capa de widgets de Material Design se construye componiendo widgets básicos a partir de la capa de Widgets. A su vez, la capa de Widgets se construye orquestando objetos de nivel inferior a partir de la capa de renderizado (Rendering). Capas superiores se construyen en base a capas inferiores, hasta llegar a la capa de código específico de cada plataforma móvil (Embedder Platform Specific).

Las capas ofrecen muchas opciones para crear aplicaciones. Elegid un enfoque personalizado para tener todo el poder expresivo del framework, o utilizad elementos disponibles de la capa de Widgets, de la capa superior Material; o simplemente mezclad y combinad. En definitiva, podéis componer combinando los widgets que Flutter proporciona, o crear vuestros propios widgets personalizados usando las mismas herramientas y técnicas que el equipo de Flutter utilizó para construir el framework. Obtendréis todos los beneficios de productividad utilizando widgets de alto nivel, pero sin sacrificar la capacidad de sumergirse en las capas inferiores.

Construyendo widgets

Las características únicas de un widget se definen implementando una función de construcción (Widget build(BuildContext context)) que devuelve el árbol (o jerarquía) de widgets que lo componen. Por ejemplo, un widget de tipo "barra de herramientas" (toolbar) puede tener una función build() que devuelva un árbol formado por un layout horizontal con algo de texto y varios widgets de tipo botón. El framework de Flutter pide entonces, como parte del proceso de actualización de la interfaz de usuario, recursivamente a cada uno de los widgets hijos que se construya hasta que el proceso llegue a los widgets hojas específicos (que no contienen otros widgets anidados). El framework auna todos los widgets y los retorna en un árbol.

La función de construcción (build()) de un widget debería estar libre de efectos secundarios. Siempre que el framework pida construir el widget se debe devolver un nuevo árbol de widgets independientemente de lo que el widget objetivo haya devuelto previamente. El framework hace el trabajo de comparar la construcción anterior con la actual y determinar qué modificaciones deben hacerse en la interfaz de usuario.

La comparación automatizada realizada por el framework es bastante efectiva, permitiendo aplicaciones interactivas de alto rendimiento. La función de construcción (build()) simplifica el código al centrarse en declarar de qué está hecho un widget y trabajar de manera recursiva.

Gestionando la interacción con el usuario

Si las características únicas de un widget necesitan cambiar en base a la interacción del usuario u otros factores, ese widget es de tipo stateful (con estado), hereda de la subclase StatefulWidget y almacena su estado (mutable) en una subclase de State. Por ejemplo, si un widget tiene un contador que se incrementa cada vez que el usuario pulsa un botón, el valor del contador es el estado de ese widget. Cuando ese valor (su estado) cambia, el widget necesita ser reconstruido para actualizar la interfaz de usuario. El diagrama siguiente ilustra el ejemplo.

Cada vez que muta un objeto State(por ejemplo, incrementando el contador), debe invocar a setState() para indicar al framework que actualice la interfaz de usuario llamando al método build() del objeto State. No os preocupéis si no entendéis del todo este proceso o tenéis dudas ahora, veremos un ejemplo de gestión del estado de un widget heredado de StatefulWidget en la plantilla MyApp que se genera con cada nuevo proyecto en Flutter.

Por otro lado, tendremos widgets de tipo StatelessWidget(widgets sin estado), que aparecen ilustrados en el diagrama de jerarquía de widgets mostrado anteriormente. Esta clase de widget es útil cuando la parte de la interfaz de usuario que está describiendo no depende más que de la información de configuración del propio objeto que se le pasa a través de su constructor. Para composiciones que pueden cambiar dinámicamente, por ejemplo, debido a que tienen un estado interno controlado por un pulso de reloj, dependiendo de algún estado del sistema, o como fruto de interacciones directas del usuario se deben usar widgets de clase StatefulWidget.

El hecho de tener separados los objetos de clase State y los objetos de clase Widget permite que otros widgets traten de la misma forma a los StatelessWidget y a los StatefulWidget, sin preocuparse por perder el estado actual de estos últimos. Un Widget padre puede crear una nueva instancia de un StatefulWidget sin perder el estado persistente del último. El framework hace todo el trabajo de encontrar y reutilizar los objetos de State existentes cuando sea apropiado.

Hasta aquí la descripción técnica de Flutter y su modelo basado en widgets. En la siguiente sesión del curso explicaremos como instalar Flutter en Windows y su configuración.

Author image
Iván González is postdoctoral researcher at the Castilla-La Mancha University.
Ciudad Real (Spain)