nov. 15, 2023
Por Alberto Méndez

Los widgets, la piedra angular de Flutter

Serie: Flutter

En el capítulo anterior hemos visto los orígenes del framework Flutter, quién lo creó, un poco sobre su comunidad, el lenguaje Dart, Skia, etc. Dimos un paseo por la superficie de esta profunda tecnología en la que intervienen múltiples actores, en esta ocasión vamos a continuar escarbando un poco más por la que podría considerarse la esencia básica de Flutter, sus widgets, cómo funcionan y qué tener en cuenta a la hora de utilizarlos para construir nuestras aplicaciones.

Flutter se basa en la idea de que todo es un widget. Desde el texto que se muestra en pantalla, los botones, las imágenes, hasta los layouts que organizan otros widgets; todo son widgets. Estos son inmutables, lo que significa que no cambian una vez creados. En lugar de ello, se crean nuevos widgets si se necesita un cambio. Esta inmutabilidad ayuda a Flutter a ser más eficiente y a ofrecer una experiencia de usuario más fluida. A diferencia de otros frameworks que pueden separar vistas, controladores, layouts y otros componentes, Flutter simplifica todo en widgets.

¿Qué es un Widget?

Puede considerarse como una descripción de una parte de la interfaz de usuario. En lugar de construir una interfaz de usuario modificando directamente los elementos en la pantalla, construyes una serie de widgets y Flutter se encarga del resto. Imagina los widgets como bloques de Lego: cada uno tiene una función y forma específica, puedes combinarlos de diversas maneras para construir algo más grande.

El framework diferencia dos tipos de Widget que serán utilizados según las condiciones del contexto, los * StatelessWidget* y StatefulWidget. Mientras que el primero es inmutable, una vez que se define no puede cambiar, el segundo es dinámico y permite que los datos que contiene cambien a lo largo del tiempo, es decir, tiene un ’estado’. Este concepto de estado es fundamental para entender cómo Flutter gestiona la interactividad y la dinámica de una aplicación. Adentrémonos en la naturaleza del estado y cómo influye en la construcción de aplicaciones en Flutter.

¿A qué nos referimos con el estado?

Cuando hablamos de aplicaciones interactivas, nos referimos a aplicaciones que pueden responder a acciones o eventos, ya sea por interacción del usuario o por eventos externos, como recibir datos de una red. Para manejar este comportamiento dinámico, Flutter introduce el concepto de “estado” en sus widgets.

Se puede decir que el estado es la información que puede cambiar durante la vida útil de un widget. Por ejemplo, si tienes un widget que muestra un contador, el número que representa el contador es su estado. Cuando el número cambia, el estado del widget ha cambiado y Flutter redibujaría el widget para reflejar ese cambio.

No todos los widgets necesitan tener estado. Algunos widgets son estáticos por naturaleza (como un widget de texto que siempre muestra el mismo valor). Otros widgets, como un checkbox o un slider, tienen un valor que puede cambiar y, por lo tanto, tienen un estado asociado.

Cuando el estado de un widget cambia, el widget se redibuja. Sin embargo, es importante entender que no estás modificando directamente el widget original; en su lugar, estás creando una nueva instancia del widget con el nuevo estado. Este enfoque garantiza que las interfaces de usuario sean altamente responsivas y se actualicen de manera eficiente.

El estado asociado con un StatefulWidget está íntimamente relacionado con el ciclo de vida de este tipo de widgets. Métodos como initState() y dispose() permiten inicializar recursos cuando se crea el estado y limpiar cuando ya no es necesario, respectivamente.

Ciclo de vida

El concepto de Ciclo de vida es esencial en programación y desarrollo, ya que comprende las distintas fases o estados por los que pasa un elemento, permitiendo a los desarrolladores gestionar cómo se inicializa, se actualiza y se desecha. En Android nativo por ejemplo existen varios puntos clave en la ejecución de un Activity como puede ser el método onCreate() que se llama la primera vez que es creado y dónde normalmente se inicializan las vistas de nuestra interfaz de usuario y otros elementos. O el método onResume() que se ejecuta cuando un Activity vuelve a ser el punto focal después de estar en pausa o detenido, ideal para reanudar tareas específicas.

Cuando el usuario toca el icono de la aplicación, el sistema operativo invoca la función main(). Esta función es el punto de entrada de todas las aplicaciones Flutter. Antes de que se pueda mostrar cualquier widget, éste debe vincularse con la plataforma nativa a través de una instancia de WidgetsBinding. La función runApp() toma el widget raíz de la aplicación y lo convierte en el árbol de widgets que se mostrará en pantalla.

Los widgets sin estado no tienen un “ciclo de vida” en el sentido tradicional ya que son inmutables, lo que significa que una vez que se les da un conjunto particular de propiedades, esas propiedades no pueden cambiar. Cada vez que necesitas cambiar la apariencia del widget, debes crear uno nuevo con las propiedades deseadas.

Los StatefulWidget poseen un ciclo de vida, ya que tienen la capacidad de cambiar a lo largo de su existencia, otorgando interactividad y dinamismo al componente en construcción. Este ciclo de vida de un widget Stateful en Flutter abarca varios puntos clave:

  • createState(): Es el primer método que se llama cuando creamos un StatefulWidget. Se encarga de crear el objeto State que se utilizará para gestionar el estado del widget.
  • initState(): Una vez que se ha creado el objeto State, el método initState() es llamado. Es aquí donde puedes realizar inicializaciones relacionadas con el estado del widget.
  • build(): Este es el método que define su interfaz y se invoca cada vez que es necesario reconstruirla, por ejemplo, cuando el estado cambia.
  • didUpdateWidget(): Se invoca cuando el widget padre cambia y necesita actualizar el widget hijo actual. Es útil para cuando necesitas realizar operaciones específicas en respuesta a cambios en los widgets padre.
  • dispose(): Es el último punto en el ciclo de vida de un StatefulWidget. Cuando el widget ya no es necesario y se va a eliminar del árbol, este método es llamado para permitir la limpieza y liberación de recursos.

Entender estos conceptos es esencial para discernir lo que sucede en nuestra aplicación en cada momento, permitiéndonos identificar las distintas fases por las que transitan nuestros componentes a lo largo de su ejecución. Continuemos viendo los diferentes tipos de Widget que existen en Flutter ahora que ya conocemos lo que es el estado y los ciclos de vida que intervienen.

Widgets con estado VS sin estado

Widget sin estado (StatelessWidget)

Los widgets sin estado (Stateless) son los más básicos en Flutter y representan una porción de la interfaz de usuario que es inmutable. Esto significa que, si quieres hacer algún cambio en un Stateless widget, en lugar de actualizarlo directamente, necesitarás crear una nueva instancia del widget con las modificaciones deseadas. Un buen ejemplo de esto es un widget Text que muestra un mensaje fijo; al ser inalterable, si quisiéramos cambiar el mensaje, deberíamos crear un nuevo widget Text con el contenido deseado.


  class MyStatelessText extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return const Text('Hola, Flutter!');
    }
  }
  

Nuestro ejemplo, exagerado a la mínima expresión nos mostraría una caja de texto con el mensaje “Hola, Flutter!” escrito en él, no proporcionaría mayor utilidad al usuario que la de mostrar una información concreta, no permite interactuar, ni cambiar nada, no tiene estado, lo que hace que su reconstrucción sea más directa y, a menudo, más eficiente. Otras de las ventajas son que hablamos de componentes intuitivos y sencillos de entender y utilizar, lo que facilita el desarrollo. Debido a que solo dependen de propiedades inmutables su comportamiento es predecible cada vez que se construyen, garantizando una experiencia uniforme. Además, su diseño sin estado permite una fácil reutilización en diferentes partes de una aplicación, mejorando la cohesión y reduciendo la redundancia en el código.

Widget con estado (StatefulWidget)

Estos sí que pueden cambiar durante su vida útil. Tienen un “estado” asociado que puede variar y hacer que el widget se redibuje. Son ideales para partes de la interfaz de usuario que necesitan ser dinámicas, como contadores, relojes, calendarios o cualquier widget que requiera interacción del usuario.

Los widgets con estado, conocidos como StatefulWidget, poseen características que les distinguen y los hacen esenciales en la creación de aplicaciones dinámicas en Flutter. Albergan datos que pueden cambiar durante la ejecución de la aplicación, tienen un estado interno que, al ser modificado, desencadena una reconstrucción del widget, permitiendo reflejar los cambios en tiempo real en la interfaz.

El ’estado’ de un StatefulWidget es esencialmente una instantánea de los datos en un momento determinado. Cada vez que este estado cambia, el método build() se invoca nuevamente, redibujando el widget con la información actualizada. Esto es especialmente útil para widgets que deben responder a entradas del usuario o a cambios en datos en tiempo real.

Un buen ejemplo de la utilidad de este tipo de widgets se encuentra en aplicaciones que requieren una respuesta interactiva. Si estás desarrollando un formulario, por ejemplo, donde los usuarios pueden ingresar y modificar información, o un juego donde los elementos cambian en respuesta a las acciones del jugador, éstos serán tu herramienta de elección.


  class StatefulCounter extends StatefulWidget {
    @override
    StatefulCounterState createState() => StatefulCounterState();
  }
  
  class StatefulCounterState extends State<StatefulCounter> {
    int _counter = 0;
  
    @override
    Widget build(BuildContext context) {
      return Column(
        children: <Widget>[
          Text('Has presionado el botón $_contador veces.'),
          ElevatedButton(
            onPressed: () {
              setState(() {
                _counter++;
              });
            },
            child: Text('Presiona aquí'),
          ),
        ],
      );
    }
  }
  

Este código fuente define un widget Stateful llamado StatefulCounter. Los widgets Stateful se componen, en realidad, de dos clases. La primera es una extensión de StatefulWidget, que es esencialmente una fábrica para su contraparte de estado. La segunda es una extensión de State, que contiene el estado mutable del widget y define cómo se ve y cómo se comporta el widget basándose en ese estado.

La clase StatefulCounter extiende StatefulWidget y sobrescribe el método createState(). Este método no construye directamente el widget con su lógica y estado, sino que devuelve una nueva instancia de StatefulCounterState, que es la que realmente maneja el estado y la lógica. Así, la clase StatefulCounter actúa como una fábrica que crea una instancia del estado asociado cuando es necesario. Esta separación permite que el widget mismo sea inmutable (como es deseable en Flutter para optimizar la reconstrucción de la UI), mientras que el estado asociado puede ser mutable y cambiar con el tiempo, como vemos con la variable _counter. En el lenguaje de programación Dart , utilizado por Flutter, el guión bajo “_” al inicio del nombre de una variable o método indica que es privado para su librería actual.

La combinación de diferentes tipos de widgets, tanto Stateful como Stateless, nos permite construir interfaces únicas y a medida para nuestras aplicaciones. Es crucial entender que son una piedra angular en la construcción de la estructura y permiten que los desarrolladores tengamos un control detallado sobre cómo y cuándo se actualiza la interfaz de usuario, brindando experiencias dinámicas y reactivas a los usuarios finales.

Diseño personalizable y reutilizable

Podríamos por ejemplo combinar un widget personalizado CustomCard con varios widgets proporcionados por Flutter: * Card*, ListTile, Icon y ElevatedButton. Lo hermoso de este enfoque es que, aunque hemos combinado múltiples widgets, la experiencia sigue siendo fluida y optimizada. Además, diseñaremos nuestra CustomCard para ser reutilizable, permitiendo la personalización del título y del ícono a través de sus propiedades. Un simple Widget que luego podríamos reutilizar en cualquier parte de nuestra aplicación sin necesidad de duplicar código importándolo con una simple línea al principio de nuestra clase.


  class CustomCard extends StatelessWidget {
    final String title;
    final IconData icon;
  
    CustomCard({required this.title, required this.icon});
  
    @override
    Widget build(BuildContext context) {
      return Card(
        elevation: 5.0,
        child: ListTile(
          leading: Icon(this.icon),
          title: Text(this.title),
          trailing: ElevatedButton(
            onPressed: () {
              // Acción al presionar
            },
            child: Text('Presiona'),
          ),
        ),
      );
    }
  }
  

La reutilización de widgets es fundamental para mantener el código limpio, modular y eficiente. No solo acelera el proceso de desarrollo, sino que también facilita la mantenibilidad del código a largo plazo. Por ejemplo, si quisiéramos cambiar el diseño de todas las tarjetas en nuestra aplicación, simplemente tendríamos que modificar el widget CustomCard, y todos los lugares donde se usa reflejarían automáticamente esos cambios.

Otra ventaja es la consistencia del diseño. Al reutilizar widgets personalizados, podemos garantizar que elementos similares en diferentes partes de la aplicación mantengan el mismo aspecto y comportamiento, lo que contribuye a una experiencia de usuario más coherente.

Con la combinación adecuada de widgets, ya sean proporcionados por el framework o creados desde cero, se pueden diseñar UIs únicas que destaquen y proporcionen experiencias memorables a los usuarios.

Hemos aprendido los conceptos más básicos de Flutter, conceptos esenciales para poder trabajar cómodamente con el framework y que no se nos atragante a la primera, en la próxima parte conoceremos el lenguaje Dart , sus características principales y algunos ejemplos de código. Si te ha gustado y quieres recibir los próximos capítulos directamente en tu bandeja de entrada no dudes en suscribirte de forma gratuita y recuerda ¡Happy Coding!

Artículos relacionados

Que te pueden interesar

¿Te ha sido útil?

Tu apoyo contribuye a que sigamos compartiendo conocimiento actualizado. Si encuentras valor en nuestros artículos, considera ayudarnos a mantener el blog en marcha. 🤗
comments powered by Disqus

Nuestro trabajo

De concepto a realidad

project-thumb
Mantente al día con las últimas noticias tecnológicas y nuestro podcast semanal. BetaZetaNews
project-thumb
Elimina las barreras del idioma de manera fluida con solo presionar un botón. BuddyLingo
project-thumb
Reserva mesa en tu restaurante favorito con MesaParaTi. La hostelería somos todos. MesaParaTi
project-thumb
Controla fácilmente y en profundidad las horas que dedicas a tu empleo y/ó proyectos. WorkIO