Diferencia entre revisiones de «Flutter StatefulWidget. Conceptos»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
Línea 932: Línea 932:
  
 
* A diferencia de la programación de aplicaciones de escritorio con otros IDE´s en los que en base a un evento. cambiamos las propiedades visuales del componente, en Flutter no se hace así...
 
* A diferencia de la programación de aplicaciones de escritorio con otros IDE´s en los que en base a un evento. cambiamos las propiedades visuales del componente, en Flutter no se hace así...
: Es decir, en la programación tradicional, si queremos cambiar el contenio de texto de una etiqueta, pondríamos algo parecido a esto:
+
: Es decir, en la programación tradicional, si queremos cambiar el contenido de texto de una etiqueta, pondríamos algo parecido a esto:
 
:<syntaxhighlight lang="java" enclose="div" highlight="" >
 
:<syntaxhighlight lang="java" enclose="div" highlight="" >
 
         final JTextField textField = new JTextField("TEXTO");
 
         final JTextField textField = new JTextField("TEXTO");

Revisión actual del 11:43 27 nov 2021

Introducción

  • Más información en:


  • Hasta ahora hemos usado los StatelessWidget para todo.
Este tipo de Widget:
  • No permite guardar ningún tipo de dato en forma de variable y cuando definimos algo a nivel de clase siempre debe estar asociado al modificador final o static const.
Si no se pone dicho modificador, no impide compilar la aplicación y aparecerá un Warning en la definición de la Clase (el nombre de la clase aparecerá subrayada).
Un ejemplo de uso sería cuando tenemos un formulario y queremos guardar lo escrito por el usuario en una variable. Esto no lo podríamos realizar con este tipo de Widget.
  • No se pueden 'redibujar' para hacer que reflejen un cambio que pueda darse por programación. Imagináos el caso de que busquéis datos por Internet y se carguen nuevos datos en la lista en la que se basa un ListView para visualizar el contenido...


En Flutter, no funciona igual que la programación tradicional con entornos gráficos, en los que se tiene una pantalla con componentes (cajas de texto, listas,...) y cambiando sus valores automáticamente se reflejan dichos cambios en el componente visual.
En Flutter, los componentes visuales (Widget´s) si visualizan en base a los datos definidos en el Widget. Si dichos datos son cambiados, es necesario redibujar el Widget para que se vuelva a visualizar con los datos modificados.
Así, si por ejemplo, tengo que una etiqueta (Widget Text) visualiza el contenido de una variable, y cambio el valor de dicha variable, es necesario redibujar el Widget etiqueta para que vuelva a visualizar el contenido cambiado.



Minuto 10: Indico que se debe devolver un EjemploStateful cuando debería ser un _EjemploStatefulState


Código explicativo del vídeo.

import 'package:flutter/material.dart';

class EjemploStateLess extends StatelessWidget {
  int _valor = 0;   // Debería ser final ya que un StatelessWidget está definido como inmutable

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('StateLessWidget'),
        ),
        body: _boton()
    );
  }

  Widget _boton(){
    return Center(child: TextButton(child:Text('Presiona $_valor'),
                                    onPressed: (){
                                        print('pulsado $_valor');
                                        _valor = _valor + 1 ;       // Aunque su valor se modifica nunca podríamos 'visualizar' su contenido actualizado.
                                    },
                                    style: TextButton.styleFrom(backgroundColor: Colors.amber),
                                    )
                  );

  }
}



class EjemploStateful extends StatefulWidget {

  @override
  _EjemploStatefulState createState() => _EjemploStatefulState();
}

class _EjemploStatefulState extends State<EjemploStateful> {
    int _valor = 10;

    @override
    Widget build(BuildContext context) {
      return Scaffold(
          appBar: AppBar(
            title: Text('appbarTitle'),
          ),
          body: _boton()
      );

    }
    Widget _boton(){
      return Center(child: TextButton(child:Text('Presiona valores $_valor'),
        onPressed: (){
          print('pulsado $_valor');
          setState(() {
            _valor++;
          });
        },
        style: TextButton.styleFrom(backgroundColor: Colors.amber),
        )
      );

    }
}



Accediendo a las propiedes del StatefulWidget desde el State

  • Cuando definimos una propiedad local dentro del StatefulWidget, podremos acceder a dicha variable dentro de la clase State empleado la palabra widget.propiedad.
Veamos un ejemplo en el que definimos la variable contador dentro del StatefulWidget botón.


import 'package:flutter/material.dart';

class EjemploStateLess extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('StateLessWidget'),
        ),
        body: Column(
          children: [
            _BotonStateful()  // Recordar que esto es equivalente a new _BotonStateless
          ],
        )
    );
  }
}

class _BotonStateful extends StatefulWidget {
  final colorAzul = Colors.blue;   
  @override
  _BotonStatefulState createState() => _BotonStatefulState();
}

class _BotonStatefulState extends State<_BotonStateful> {
  int _contador = 0;

  @override
  Widget build(BuildContext context) {
    return Center(child: TextButton(child:Text('Botón Stateful ${widget._contador}'),
      style: TextButton.styleFrom(backgroundColor: widget.colorAzul), onPressed: () { setState(() {
        _contador++;  
      }); },
    )
    );
  }
}


  • Nota: Lo 'normal' es que las constantes estén definidas en el Stateful (partes invariantes) y las variables en el State asociado.


Combinando Widgets

  • Indicar que dentro de una clase podemos tener diferentes Widget de tipo Stateless o Stateful.
En el siguiente ejemplo, dentro de un StatelessWidget creo dos Widgets botones, uno de tipo StatelessWidget y otro de tipo StatefulWidget.
Ya veremos posteriormente como podemos pasar datos entre Widgets y entre pantallas diferentes.
En el ejemplo creo una clase (Datos) que guarda el valor que quiero enviar a los dos botones.
Al pulsar sobre el StatelesWidget, puedo incrementar el valor de dicha variable, pero no puedo redibujar el Widget, por lo que el contador aumentará pero no se visualizará hasta que pulse sobre el StatefulWidget el cual se redibujará con el nuevo valor.
Flutter dart stateful 1.JPG


import 'package:flutter/material.dart';

class _Datos{
  int valor = 0;
}
final dato = new _Datos();


class EjemploStateLess extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('StateLessWidget'),
        ),
        body: Column(
          children: [
            _BotonStateless(),    // Recordar que esto es equivalente a new _BotonStateless
            _BotonStateful()
          ],
        )
    );
  }
}


class _BotonStateful extends StatefulWidget {
  @override
  _BotonStatefulState createState() => _BotonStatefulState();
}

class _BotonStatefulState extends State<_BotonStateful> {

  @override
  Widget build(BuildContext context) {
    return Center(child: TextButton(child:Text('Botón Stateful ${dato.valor}'),
      style: TextButton.styleFrom(backgroundColor: Colors.amber), onPressed: () { setState(() {
        dato.valor++;
      }); },
    )
    );
  }
}



class _BotonStateless extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(child: TextButton(child:Text('Botón Stateless ${dato.valor}'),
        style: TextButton.styleFrom(backgroundColor: Colors.amber), onPressed: () { dato.valor++; },
      )
    );

  }
}



Ejemplos de uso

  • Voy a explicar a continuación varios ejemplos que irán completando la explicación anterior...
  • En todos los ejemplos, salvo que se indique lo contrario, debéis hacer lo mismo:
Creamos una nueva página en la carpeta pages/stateful/aprender de nombre aprenderNUM_page.dart (crear los directorios dentro de lib si no están creados previamente). NUM es el número de ejemplo.
En todas las páginas haremos lo mismo:
  • Importamos la librería de 'material' (recordar que existe un snippet que lo hace automático).
  • Creamos una clase (AprenderNUMPage) que derive de StatefulWidget. Se creará (si lo hacéis con un Snippet) una clase asociada que deriva de State. NUM es el número de ejemplo.
  • Modificamos la clase State y en su métod build hacemos que devuelva un Scaffold con una Appbar (hacerlo con un Snippet)



Ejemplo 1

  • En este ejemplo, partimos que todo nuestro Widget Scaffold es un StatefulWidget.
Dentro del mismo, todos los Widget que definamos 'localmente', van poder llamar al método setState para redibujar el StatefulWidget y por tanto redibujará toda la pantalla.
  • Objetivo: Modificar el tamaño (ancho) de un container el pulsar sobre el botón.
Flutter dart exemplo 1 stateful.JPG


Solución: Para lograr este objetivo, necesitamos emplear una variable (ya que su valor va a aumentar al pulsar el botón) y asociar dicha variable al ancho del contenedor.
Por lo tanto, al pulsar sobre el botón, es necesario modificar el valor de la variable y redibujar el contenedor (en este momento no sabemos hacer eso, sabemos redibujar el StatefulWidget que es la pantalla entera, y por tanto se va a redibujar el botón, el contenedor, la AppBar,es decir, toda la pantalla.


import 'package:flutter/material.dart';
/**
 * Neste exemplo aprendemos a diferenza entre Stateless e Stateful
 * Temos que entender como funcionan os Widgets en Flutter.
 */

class Aprender1Page extends StatefulWidget {
  const Aprender1Page({Key? key}) : super(key: key);

  @override
  _Aprender1PageState createState() => _Aprender1PageState();
}

class _Aprender1PageState extends State<Aprender1Page> {
  double _ancho = 50;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Aprender1 Statefulwidget'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                padding: EdgeInsets.all(10),
                width: _ancho,
                height: 50,
                color: Colors.indigo,
              ),
              TextButton(
                child: Text('PRESIONAME'),
                style: TextButton.styleFrom(backgroundColor: Colors.black),
                onPressed: () {
                  setState(() {
                    _ancho+=20;
                  });
                },
              )
            ],
          ),
        )
    );
  }
}



Ejemplo 2

  • En este ejemplo, partimos que todo nuestro Widget Scaffold es un StatefulWidget.
Dentro del mismo, todos los Widget que definamos 'localmente', van poder llamar al método setState para redibujar el StatefulWidget y por tanto redibujará toda la pantalla.
  • Objetivo: En este ejemplo, disponemos de 3 botones. Cada botón va a llevar un contador de cuantos veces se ha pulsado, y también cuantas veces se han pulsado todos los botones.
El número de veces pulsado / total, se visualizará en el texto de botón.


Flutter dart exemplo 2 stateful.JPG


  • Solución: Necesitamos emplear 4 variables. Una para cada botón, y que guardará información de cuantas veces se ha pulsado dicho botón, y otra que guardará la suma de todos los botones pulsados.
import 'package:flutter/material.dart';

class Aprender2Page extends StatefulWidget {
  const Aprender2Page({Key? key}) : super(key: key);

  @override
  _Aprender2PageState createState() => _Aprender2PageState();
}

class _Aprender2PageState extends State<Aprender2Page> {
  int _cont1=0, _cont2=0,_cont3=0;
  int _contTotal = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Aprender2 Statefulwidget'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              _boton1(),
              _boton2(),
              _boton3()
            ],
          ),
        )
    );
  }

  Widget _boton1(){
    return TextButton(
        onPressed: () {
          setState(() {
            _cont1++;
            _contTotal++;
          });
        },
        child: Text('Pulsado $_cont1. Total veces $_contTotal')
    );
  }
  Widget _boton2(){
    return TextButton(
        onPressed: () {
          setState(() {
            _cont2++;
            _contTotal++;
          });
        },
        child: Text('Pulsado $_cont2. Total veces $_contTotal')
    );
  }
  Widget _boton3(){
    return TextButton(
        onPressed: () {
          setState(() {
            _cont3++;
            _contTotal++;
          });
        },
        child: Text('Pulsado $_cont3. Total veces $_contTotal')
    );
  }
}


  • Si analizáis un poco el código es daréis cuenta que no está muy 'optimizado' ya que por cada botón tenemos el mismo código, variando el número de veces que se pulsa ese botón.
Lo ideal sería, de alguna forma, que el botón 'supiera' cual es la variable contador asociada al mismo.


¿ Se os ocurre alguna idea ? :)



Posible Solución: Una forma (que no la única) con lo que sabemos hasta ahora, podría ser guardar los valores 'locales' de cada botón en una lista. En ese caso, el botón 1 tendría que modificar la posición 0 de la lista, el botón 2, la posición segunda de la lista y el botón 3 la tercera posición. Por lo tanto, cuando llamo a la función que representa el Widget botón, podría enviarle la posición a modificar.


import 'package:flutter/material.dart';

class Aprender2Page extends StatefulWidget {
  const Aprender2Page({Key? key}) : super(key: key);

  @override
  _Aprender2PageState createState() => _Aprender2PageState();
}

class _Aprender2PageState extends State<Aprender2Page> {
  List<int> _listaPulsaciones = [0,0,0];
  int _contTotal = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Aprender2 Statefulwidget'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              _boton(0),
              _boton(1),
              _boton(2),
            ],
          ),
        )
    );
  }

  Widget _boton(int posLista){
    return TextButton(
        onPressed: () {
          setState(() {
            _listaPulsaciones[posLista]++;
            _contTotal++;
          });
        },
        child: Text('Pulsado ${_listaPulsaciones[posLista]} Total veces $_contTotal')
    );
  }
}




Ejemplo 3

  • En este ejemplo, partimos que todo nuestro Widget Scaffold es un StatefulWidget.
Dentro del mismo, todos los Widget que definamos 'localmente', van poder llamar al método setState para redibujar el StatefulWidget y por tanto redibujará toda la pantalla.
  • Objetivo: Al pulsar sobre un elemento de la lista, este queda seleccionado visualmente y una etiqueta muestra el valor seleccionado.
Al pulsar durante unos segundos sobre un elemento de la lista, el elemento desaparece.
Emplear un ListTile como Widget de cada elemento y poder gestionar los dos eventos y recordar que podemos 'envolverlo' en un Widget Ink para darle un color de fondo y un efecto al pulsar.
Indicar que en cualquier propiedad de cualquier Widget podéis llamar a una función o tener un código que devuelva el tipo de dato que espera dicha propiedad.
Flutter dart exemplo 3 stateful.JPG


  • Solución: Los datos de la lista van a estar en un List. Necesitamos guardar el elemento seleccionado (o su posición) para que cuando se 'dibuje' comprobar si el elemeto que se está dibujando es el elemento seleccionado, y en ese caso, poner un color de fondo al mismo.
La propiedad que queremos cambiar es color del Widget Ink.
Por otro lado, al pulsar dos veces necesitamos eliminar el elemento de la lista y redibujar la lista.
Vamos a tener que 'redibujar' el Widget tanto cuando pulsemos un elemento de la lista, como cuando


import 'package:flutter/material.dart';

class Aprender3Page extends StatefulWidget {
  const Aprender3Page({Key? key}) : super(key: key);

  @override
  _Aprender3PageState createState() => _Aprender3PageState();
}

class _Aprender3PageState extends State<Aprender3Page> {
  List<String> _listaElementos = ['Elemento1','Elemento2','Elemento3'];
  String _elemSeleccionado='';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Aprender3 Statefulwidget'),
        ),
        body: Center(
          child: _obtenerLista()
        )
    );
  }

  Widget _obtenerLista(){

    return ListView(
      children: _listaElementos.map((e) => _elementoLista(e)).toList(),
    );
  }

  Widget _elementoLista(String elemento){
    return Ink(
      color: _elemSeleccionado == elemento ? Colors.red : Colors.indigo,
      child: ListTile(
        title: Text('$elemento'),
        onTap: () {
          setState(() {
            _elemSeleccionado = elemento;
          });
        },
        onLongPress: (){
          setState(() {
            _listaElementos.remove(elemento);
            _elemSeleccionado = '';
          });
        },
      ),
    );
  }

}



Ejemplos Stateless-Stateful combinados

  • Hasta ahora toda nuestra pantalla era un StatefulWidget, en la que guardábamos de forma 'global' las variables que necesitábamos para los Widgets que conformaban la pantalla, y dichos Widgets podían acceder a estas variables y llamar al método setState al estar definidos 'dentro' de la clase State del Stateful Widget.
Pero en una situación real, esto normalmente no sucede. Los Widget´s los tendremos definidos en archivos diferentes y en clases diferentes.
  • Las combinaciones entre Widget´s pueden ser:
  • StatelessWidget - StatefulWidget
  • StatelessWidget - StatelessWidget (ya vista)
  • StatefulWidget - StatelessWidget
  • StatefulWidget - StatefulWidget
  • Y además, estas combinaciones se pueden dar entre widgets que conforman diferentes pantallas, que conforman la misma pantalla y se encuentran al mismo nivel en el árbol de jerarquía de Widget, o que se encuentra una dentro de otro en el árbol.


Ejemplo 4

CASO StatelessWidget -> StatefulWidget: Un StatefulWiget dentro de un StatelessWidget en la jerarquía.


  • En este ejemplo, partimos que la pantalla principal (el Scaffold) es un StatelessWidget y que empleamos un Widget Stateful para visualizar una lista de elementos.
Los datos están en el Stateless y necesitamos pasarlos al StatefulWidget.
El Stateful hará uso de dichos datos y podrá modificarlos redibujándose cuando lo necesite (sólo se redibujará
Para ello, empleamos el constructor de StatefulWidget.
Nota: Veremos posteriormente las diferentes formas que tiene Flutter de compartir información entre Widget.
  • Objetivo: Al pulsar sobre un elemento de la lista, este queda seleccionado visualmente y una etiqueta muestra el valor seleccionado.
Al pulsar durante unos segundos sobre un elemento de la lista, el elemento desaparece.
Emplear un ListTile como Widget de cada elemento y poder gestionar los dos eventos y recordar que podemos 'envolverlo' en un Widget Ink para darle un color de fondo y un efecto al pulsar.
La lista debe estar definida en un StatefulWidget y el Scaffold en un StatelessWidget.
Flutter dart exemplo 3 stateful.JPG
import 'package:flutter/material.dart';

class Aprender4Page extends StatelessWidget {
  // Fijarse que al estar en un StatelessWidget, los datos deben ser guarados en una constante.
  final List<String> _listaElementos = ['Elemento1','Elemento2','Elemento3'];

  Aprender4Page({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Dibuja el Scaffold');
    return Scaffold(
        appBar: AppBar(
          title: Text('Aprender3 Statefulwidget'),
        ),
        body: Center(
            child: WidgetLista(listaElementosConstante: _listaElementos,)
        )
    );
  }
}

class WidgetLista extends StatefulWidget {
  // Un statefulwidget también es inmutable. Sólo admite variables en la clase State
  final List<String> listaElementosConstante; // A paser que la lista es final, se puede modificar.

  const WidgetLista({Key? key, required this.listaElementosConstante}) : super(key: key);

  @override
  WidgetListaState createState() => WidgetListaState();
}

class WidgetListaState extends State<WidgetLista> {
  String _elemSeleccionado='';

  @override
  Widget build(BuildContext context) {
    print('Dibuja la lista:${widget.listaElementosConstante}');

    return ListView(
      children: widget.listaElementosConstante.map((e) => _elementoLista(e)).toList(),
    );
  }


  Widget _elementoLista(String elemento){
    return Ink(
      color: _elemSeleccionado == elemento ? Colors.red : Colors.indigo,
      child: ListTile(
        title: Text('$elemento'),
        onTap: () {
          setState(() {
            _elemSeleccionado = elemento;
          });
        },
        onLongPress: (){
          setState(() {
            widget.listaElementosConstante.remove(elemento);
            _elemSeleccionado = '';
          });
        },
      ),
    );
  }

}



  • Vamos a hacer una modificación al ejemplo anterior, y vamos a pasar desde el StatefulWidget, aparte de la lista, un número que será el color inicial de fondo de los elementos de la lista. Al pulsar sobre un elemento de la lista, el valor del color se incrementará en 10 hasta llegar a 255 y volverá a cero.
En este caso, al pasar el dato al StatefulWidget, el color se guarda en una constante, por lo que desde la clase State no hará caso a ninguna modificación que agamos sobre la misma, empleando la referencia widget.color.
Por lo tanto, debemos de definir una variable color en la clase State e igualarla a la que está en la clase Stateful asociada, pero sólo una vez !!!!, ya que lo hacemos en el método built y dicho método se llama cada vez que se ejecuta setState.
La otra forma sería enviarlo en el constructor de la clase State.
Este caso normalmente no se da y emplearemos un provider (lo veremos más adelante) para gestionar este caso.
import 'package:flutter/material.dart';

class Aprender4Page extends StatelessWidget {
  // Fijarse que al estar en un StatelessWidget, los datos deben ser guarados en una constante.
  final List<String> _listaElementos = ['Elemento1','Elemento2','Elemento3'];
  final int colorAPasar = 1;

  Aprender4Page({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Dibuja el Scaffold');
    return Scaffold(
        appBar: AppBar(
          title: Text('Aprender3 Statefulwidget'),
        ),
        body: Center(
            child: WidgetLista(listaElementosConstante: _listaElementos,colorConstante: colorAPasar,)
        )
    );
  }
}

class WidgetLista extends StatefulWidget {
  // Un statefulwidget también es inmutable. Sólo admite variables en la clase State
  final List<String> listaElementosConstante; // A paser que la lista es final, se puede modificar.
  final int colorConstante;

  const WidgetLista({Key? key, required this.listaElementosConstante,
                    required this.colorConstante}) : super(key: key);

  @override
  WidgetListaState createState() => WidgetListaState();
}

class WidgetListaState extends State<WidgetLista> {
  int _colorVariable = -1;
  String _elemSeleccionado='';

  @override
  Widget build(BuildContext context) {
    if (_colorVariable==-1)
        _colorVariable = widget.colorConstante;

    print('Dibuja la lista:${widget.listaElementosConstante}');
    print('Color:$_colorVariable');

    return ListView(
      children: widget.listaElementosConstante.map((e) => _elementoLista(e)).toList(),
    );
  }


  Widget _elementoLista(String elemento){
    return Ink(
      color: _elemSeleccionado == elemento ? Colors.red : Color.fromARGB(255, _colorVariable, _colorVariable, _colorVariable),
      child: ListTile(
        title: Text('$elemento'),
        onTap: () {
          setState(() {
            _colorVariable = _colorVariable < 245 ? _colorVariable+10 : 0;
            _elemSeleccionado = elemento;
          });
        },
        onLongPress: (){
          setState(() {
            widget.listaElementosConstante.remove(elemento);
            _elemSeleccionado = '';
          });
        },
      ),
    );
  }

}



Ejemplo 5

CASO StatefulWidget -> StatelessWidget: Un StatelessWidget dentro de un StatefulWiget en la jerarquía.

  • Entre los casos de funcionamiento que nos podemos encontrar tenemos:
  • Visualizar un dato que vengal del StatefulWidget => No tiene problema. Se le pasaría por el constructor.
  • Que el StatelessWidget quiera provocar que se redibuje el StatefulWidget => No lo podría hacer ya que al no ser un StatefulWidget no puede llamar al método setState.
  • La solución que se propone para estos casos es la de enviar una función desde el StatefulWidget al StatelessWidget. El StatelessWidget llamará a la función que se ejecutará en el StatefulWidget y por lo tanto ya puede llamar al método setState.


  • Veamos el Ejemplo1 modificado para este caso.
El botón es un StatelessWidget y el contenedor está dentro del StatefulWidget.
Queremos que al pulsar el botón se cambie el tamaño del contenedor.


Flutter dart exemplo 1 stateful.JPG


import 'package:flutter/material.dart';


class Aprender5Page extends StatefulWidget {
  const Aprender5Page({Key? key}) : super(key: key);

  @override
  _Aprender5PageState createState() => _Aprender5PageState();
}

class _Aprender5PageState extends State<Aprender5Page> {
  double _ancho = 50;

  void aumentarAncho(int valor){
    setState(() {
      _ancho+=valor;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Aprender1 Statefulwidget'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                padding: EdgeInsets.all(10),
                width: _ancho,
                height: 50,
                color: Colors.indigo,
              ),
              TextoStateless(actualizarContenedor: aumentarAncho,)
            ],
          ),
        )
    );
  }
}

class TextoStateless extends StatelessWidget {
  Function actualizarContenedor;
  TextoStateless({Key? key, required this.actualizarContenedor}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextButton(
      child: Text('PRESIONAME'),
      style: TextButton.styleFrom(backgroundColor: Colors.black),
      onPressed: () {
       actualizarContenedor(20);
      },
    );
  }
}



Ejemplo contador

  • Podemos analizar ahora el ejemplo 'Hola Mundo' que aparece cuando se instala Flutter y que podemos visualizar en la página dartpad.dev



Ejemplo de varios StatefulWidget

En todas las páginas haremos lo mismo:
  • Importamos la librería de 'material' (recordar que existe un snippet que lo hace automático).
  • Creamos una clase que derive de StatelessWidget (al hacerlo será necesario implementar el método build, ya os da un aviso el Android Studio).
  • Dentro del método build, retornamos un widget Scaffold (recordar que existe un snippet que lo hace automático).


  • En este ejemplo podemos ver como disponemos de 3 Containers, y al pulsarlos, la variable local a dichos contenedores se incrementa.
Dentro del child del Scaffold retornamos un widget Row, donde situaremos los 3 contenedores.


Flutter dart stateful 2.JPG


Para controlar el evento de pulsar sobre un contenedor hago uso de la clase InkWell que permite tener un efecto al pulsar sobre el contenedor.
import 'package:flutter/material.dart';

class VariosStateFull extends StatelessWidget {
  const VariosStateFull({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Varios StatefulWidget'),
        ),
        body: Center(
          child: _variosContenedores(),
        ));
  }

  Widget _variosContenedores(){
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Contenedor(color: Colors.green),
        Contenedor(color: Colors.red),
        Contenedor(color: Colors.yellow),

      ],
    );
  }

}

class Contenedor extends StatefulWidget {
  final Color color;
  const Contenedor({Key? key, required this.color}) : super(key: key);

  @override
  _ContenedorState createState() => _ContenedorState();
}

class _ContenedorState extends State<Contenedor> {

  int _contador=0;
  _ContenedorState(){

  }
  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Container(
        child: Text('$_contador'),
        color: widget.color,
        width: 50,
        height: 50,
        margin: EdgeInsets.all(12.0),
      ),
      onTap: (){ setState(() {
        _contador++;
      });},
    );
  }
}





Cuando crear un StatelessWidget o un StatefulWidget

  • Como norma general, siempre debes de crear un StatelessWidget y cuando al programar veas que es necesario guardar el estado, convertirlo a un StatefullWidget.
En Android Studio (y en VSCode también) es inmediato convertilo a un StatefulWidget.


  • Como pista para saber si necesitamos un Stateless o un Stateful, siempre que el Widget tenga algún camnbio 'visual' en base a eventos que se produzcan sobre la pantalla, será un firme candidato a ser un StatefulWidget.



  • A diferencia de la programación de aplicaciones de escritorio con otros IDE´s en los que en base a un evento. cambiamos las propiedades visuales del componente, en Flutter no se hace así...
Es decir, en la programación tradicional, si queremos cambiar el contenido de texto de una etiqueta, pondríamos algo parecido a esto:
        final JTextField textField = new JTextField("TEXTO");
        JButton button = new JButton("Click!");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                textField.setText("NUEVO TEXTO");
            }
        });
Como vemos, capturamos el evento de Click sobre el botón, y en el método actionPerformed cambiamos la propiedad text del objeto JTextField.


  • En la programación con Flutter ESTO NO ES POSIBLE.
Debemos de cambiar la mentalidad y pensar que los cambios en las propiedades de un Widget, se establecen mediante variables cuyos valores cambian antes de redibujarse...
Por tanto, para redibujarse será necesario que sea un StatefulWidget (o que lo sea algún Widget que envuelva al StatelessWidget) y redibujarse se redibujará con los valores de las variables cambiadas previamente.
Veamos el mismo ejemplo desde la perspectiva de Flutter:
class _CambiandoTextoState extends State<CambiandoTexto> {
  String _texto = 'TEXTO INICIAL';
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () {
          setState(() {
            _texto = 'TEXTO CAMBIADO';
          });
        },
        child: Text('$_texto')
    );
  }
}


Vemos que en la línea 12, el texto se dibuja con el contendo de una variable definida en la clase State.
Cuando pulsamos sobre el texto, llamamos al método setState. Dentro de la función, cambiamos el valor de la variable que utilizar el Widget. La llamada a setState va a provocar que el widget se redibuje (sólo llama al método build). Como hemos cambiado el valor de la variable _texto, al redibujarse, cambia el texto...





Ejercicios propuestos

  • Crea una variable global que guarde en un lista los valores de los tres contenedores.
Haz que cada contenedor esté identificado por un id (0,1,2) que se corresponda con la posición de la lista a la que tendrá que acceder a modificar su valor.
En base al id (0,1,2) haz que al presionar el contenedor, aumente el valor del elemento de la lista y que se visualice en el texto que hay en el interior.
¿ Cómo harías para que en el caso de que se actualizara en contador de todos los contenedores al pulsar en cualquiera de ellos ?



Solución ejercicio propuesto:

import 'package:flutter/material.dart';

var datos = <int>[1,2,3];

class VariosStateFull extends StatelessWidget {
  const VariosStateFull({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Varios StatefulWidget'),
        ),
        body: Center(
          child: _variosContenedores(),
        ));
  }

  Widget _variosContenedores(){
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Contenedor(color: Colors.green,id: 0),
        Contenedor(color: Colors.red, id: 1),
        Contenedor(color: Colors.yellow,id: 2),

      ],
    );
  }

}

class Contenedor extends StatefulWidget {
  final Color color;
  final int id;
  const Contenedor({Key? key, required this.color,required this.id}) : super(key: key);

  @override
  _ContenedorState createState() => _ContenedorState();
}

class _ContenedorState extends State<Contenedor> {

  int _contador=0;
  _ContenedorState(){

  }
  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Container(
        child: Text('${datos[widget.id]}'),
        color: widget.color,
        width: 50,
        height: 50,
        margin: EdgeInsets.all(12.0),
      ),
      onTap: (){ setState(() {
        datos[widget.id]++;
      });},
    );
  }
}


Pregunta: ¿ Cómo harías para que en el caso de que se actualizara en contador de todos los contenedores al pulsar en cualquiera de ellos ?
Respuesta: Habría que crear un único StatefulWidget que 'envuelva' a los 3 contenedores.



Enlace a la página principal de la UD4

Enlace a la página principal del curso




-- Ángel D. Fernández González -- (2021).