latest

Introducción a la programación basada en widgets. Stateless widgets

En la sesión anterior de este curso de Flutter realizamos nuestra primera aplicación sencilla desde cero siguiendo el estilo visual material design. Hoy introduciremos los StatelessWidget o "widgets de estado inmutable", viendo como podemos crear nuestros propios widgets de esta clase.

Como en ocasiones anteriores, creamos un nuevo proyecto en Flutter y eliminamos el contenido de lib/main.dart para comenzar nuestro ejemplo de aplicación desde cero. Importamos el paquete de widgets de material.dart y definimos el método main() con un Widget de clase MaterialApp. En esta ocasión no vamos a añadir un Widget Scaffold directamente a la propiedad home del widget MaterialApp (su ruta inicial "/"). No, en esta ocasión vamos a añadir nuestro propio Widget de clase StatelessWidget que denominaremos MiStatelessWidget. El código básico para hacer esto es mostrado a continuación.

import 'package:flutter/material.dart';

void main() {
  runApp(
    new MaterialApp(
      home: new MiStatelessWidget(), 
    ),
  );
}

class MiStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      
    );
  }
}

Lo mostrado arriba es el código básico o, por decirlo de otra forma, el "esqueleto" mínimo que requiere la construcción de nuestro propio StatelessWidget. Como podéis observar la clase MiStatelessWidget hereda de StatelessWidget y sobreescribe el método build() que es el encargado del renderizado. Éste será el lugar donde añadiremos el árbol de widgets que compondrán nuestro MiStatelessWidget.

A lo largo del curso, vamos a repetir muchas veces esta estructura de código básica cada vez que construyamos un widget que herede de StatelessWidget.  Partiendo de esta estructura, añadiremos propiedades a nuestro StatelessWidget y adaptaremos el código de construcción (build()) en función de éstas.

Los StatelessWidget, como se ha venido comentando anteriormente, son widgets de estado inmutable, esto quiere decir que cuando generamos un Widget de este tipo lo hacemos con determinados contenidos (propiedades) que se indican a través de su constructor y que van a permanecer inalterables (o inmutables) durante el ciclo de vida del Widget.  

Partiendo de como definir el esqueleto mínimo de widgets de clase StatelessWidget vamos implementar una nueva aplicación Flutter con el siguiente código fuente:

import 'package:flutter/material.dart';

void main() {
  runApp(
    new MaterialApp(
      home: new MiStatelessWidget(), 
    ),
  );
}

class MiCard extends StatelessWidget {
  
  // Propiedades
  final Widget titulo;
  final Widget icono;

  // Constructor
  MiCard({this.titulo,this.icono});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: new Card(
        child:Column(
          children: [
            this.titulo, 
            this.icono,
          ],
        )
      ),
    );
  }
}

class  MiStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: new AppBar(
        title: new Text("¿Qué te parece el curso?")
      ),
      body: new Center(
        child: new Column(
          children: [
            new MiCard(
              titulo: new Text("Me gusta"), 
              icono: new Icon(Icons.thumb_up),
            ),
            new MiCard(
              titulo: new Text("No me gusta"), 
              icono: new Icon(Icons.thumb_down),
            ),
            new MiCard(
              titulo: new Text("Ni fu, ni fa"), 
              icono: new Icon(Icons.thumbs_up_down),
            ),
          ],
        )
      )
    );
  }
}

Antes de entrar a explicar en detalle el código os mostramos una imagen capturada de la pantalla de Home que se obtiene con él.

Es una interfaz de usuario muy básica con tres opciones que indican el grado de satisfacción con un determinado curso. Cada opción está representada por un texto y un icono.

Desde el punto de vista de la implementación, las opciones de satisfacción son tres StatelessWidget que hemos construido nosotros mismos (heredando de StatelessWidget) y que hemos denominado MiCard, dado que internamente emplean un Widget de tipo Card muy utilizado en material design. Un Card no es más que un contenedor con las esquinas ligeramente redondeadas y sombreadas.

Cada MiCard contiene dos Widget a modo de propiedades (titulo e icono). Estas propiedades se inicializan a través de su constructor como parámetros opcionales nombrados MiCard({this.titulo,this.icono}), como puede comprobarse en el código expuesto anteriormente.

Así pues, es en el cuerpo del método sobreescrito build() de MiCard donde componemos la estructura jerárquica de nuestro StatelessWidget . Concretamente, utilizamos un Widget Container para envolver un Widget de material design de clase Card. A su vez, el Widget Card contiene un Widget de clase Column, que no hemos  usado hasta ahora y que permite construir una columna de elementos de tipo Widget. Para ello, dispone de la propiedad children compuesta por una estructura de tipo List<Widget>, donde cada Widget incluido representa una fila. Para la propiedad icono utilizamos el Widget Icon y elegimos entre los múltiples iconos que nos facilita Flutter.

Finalmente, los widgets de tipo MiCard son empleados en otro StatelessWidget propio (denominado MiStatelessWidget). En el método build(), implementamos un árbol de widgets que parte de un Scaffold con un AppBar que recoge el texto "¿Qué te parece el curso?" y con un Widget de centrado en su propiedad body. Hijo del Widget de centrado hay otro de clase Column que recoge en su propiedad children los tres Widget MiCard (para los 3 estados de satisfacción).

Sin embargo, todos estaremos de acuerdo que, estéticamente, podemos realizar algunas mejoras a nuestra aplicación básica. Primero vamos a añadir la propiedad crossAxisAlignment: CrossAxisAlignment.stretch al Widget de clase Column de MiStatelessWidget. Esto extenderá (horizontalmente) los elementos MiCard a todo el espacio de la pantalla disponible.

También podemos separar los elementos MiCard entre sí dando algo de padding al Container que envuelve el Widget Card. Por ejemplo, padding únicamente en la parte superior empleando la propiedad padding: const EdgeInsets.only(top: 2.0).

Un pequeño inciso, para tener acceso rápido a los nombres de propiedades, widgets, métodos, etc., os recomendamos que utilicéis el comando CTRL+ESPACIO para desplegar la característica IntelliSense de VS Code.

Estaría bien añadir algo de padding dentro de cada Widget Card para lograr el siguiente aspecto:

La clase MiCard quedaría de la siguiente manera incorporando las propiedades de padding anteriormente mencionadas:

class MiCard extends StatelessWidget {
  
  // Propiedades
  final Widget titulo;
  final Widget icono;

  // Constructor
  MiCard({this.titulo,this.icono});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.only(top: 2.0),
      child: new Card(
        child:Container(
          padding: const EdgeInsets.all(15.0),
          child:Column(
            children: [
              this.titulo, 
              this.icono,
            ],
          )
        )
      ),
    );
  }
}

Por último, podríamos hacer el texto y los iconos más grandes construyendo los Widget MiCard en MiStatelessWidget de manera similar a esta:

new MiCard(
  titulo: new Text("Me gusta",
  style: new TextStyle(fontSize: 21.0),), 
  icono: new Icon(Icons.thumb_up, size: 80.0,),
),

Nuestra aplicación de ejemplo luciría así:

Y el código siguiente sería el código final de nuestra aplicación Flutter de ejemplo:

import 'package:flutter/material.dart';

void main() {
  runApp(
    new MaterialApp(
      home: new MiStatelessWidget(), 
    ),
  );
}

class MiCard extends StatelessWidget {
  
  // Propiedades
  final Widget titulo;
  final Widget icono;

  // Constructor
  MiCard({this.titulo,this.icono});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.only(top: 2.0),
      child: new Card(
        child:Container(
          padding: const EdgeInsets.all(15.0),
          child:Column(
            children: [
              this.titulo, 
              this.icono,
            ],
          )
        )
      ),
    );
  }
}

class  MiStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final tamanyoIcono = 80.0; 
    final tamanyoTexto = 21.0; 
    return Scaffold(
      appBar: new AppBar(
        title: new Text("¿Qué te parece el curso?")
      ),
      body: new Center(
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            new MiCard(
              titulo: new Text("Me gusta",
              style: new TextStyle(fontSize: tamanyoTexto),), 
              icono: new Icon(Icons.thumb_up, size: tamanyoIcono,),
            ),
            new MiCard(
              titulo: new Text("No me gusta", 
              style: new TextStyle(fontSize: tamanyoTexto),), 
              icono: new Icon(Icons.thumb_down, size: tamanyoIcono,),
            ),
            new MiCard(
              titulo: new Text("Ni fu, ni fa",
              style: new TextStyle(fontSize: tamanyoTexto),),  
              icono: new Icon(Icons.thumbs_up_down, size: tamanyoIcono,),
            ),
          ],
        )
      )
    );
  }
}

En este punto debemos remarcar que nuestra interfaz de usuario es completamente inútil. Naturalmente, algo así tendría sentido si en lugar de iconos usáramos botones (RaisedButton, FlatButton, ...), si bien, ese tipo de widgets no son inmutable, es decir, cambian su aspecto en función de su estado interno como veremos en la sesión siguiente dedicada a los StatefulWidget.  

Hasta aquí la sesión introductoria de programación basada en widgets que ha estado centrada en los StatelessWidget.

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