Flutter — StatelessWidget y StatefulWidget


StatelessWidget

El StatelessWidget genera un widget que no requiere estado mutable. Estos widgets sin estados son útiles cuando el objeto en si no tendrá ninguna modificación en compilación o no tiene atributos que modificar, su estado se mantendrá igual que cuando se crea.

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
      ),
      home: const HomePage(title: 'AppBar'),
    );
  }
}

StatefulWidget

Ya vimos que los StatelessWidget no tienen un estado, ahora con los StatefulWidget pasa lo contrario, tienen un estado que lo representa la clase State. Este State es la información que se lee cuando se construye el widget y puede cambiar mientras el widget esté activo. Estos widgets son útiles cuando necesitamos realizar cambios en el estado y hacerlos persistentes durante la vida útil del widget.

Ciclo de vida del State.

createState()

Este es el primer método que ejecuta Flutter, y sirve para crear el estado (State) e insertarlo en el árbol. El State creado va asociado a un BuildContext (nos da la ubicación de un widget dentro del árbol de widgets) durante todo su ciclo. En este punto el widget ya se considera montado, es decir, si validación el valor de la variable mounted sería true.

class HomePage extends StatefulWidget {
  const HomePage({super.key, required this.title});

  final String title;

  @override
  State<HomePage> createState() {
    print('createState()');
    return _HomePageState();
  }
}

initState()

Luego de crear el State, Flutter llama a initState(), para inicializar el State, si se quiere realizar otro tipo de inicialización en una subclase de State se debe sobreescribir el método.

  @override
  void initState() {
    print('initState()');
    print('mounted: $mounted');
    _counter = 0;

    super.initState();
  }

didChangeDependencies()

Luego del initState(), Flutter llama a didChangeDependencies(), que se llama cuando una dependencia de State cambia. Se llama inicialmente para realizar la inicialización involucrando InheritedWidgets (clase para propagar eficientemente la información por el árbol de widgets). 

  @override
  void didChangeDependencies() {
    print('didChangeDependencies()');
    super.didChangeDependencies();
  }

En este punto ya el widget está correctamente inicializado.

build()

El widget llama al método build(), cuando el widget está inicializado, se puede llamar varias veces para definir la interfaz de usuario para este subárbol. 

  @override
  Widget build(BuildContext context) {
    print('build()');

    return Container();
  }

setState()

Los objetos de State pueden actualizar y reconstruir su subárbol llamando a su método setState(), lo que indica a Flutter que han habido cambios que podrían afectar la interfaz de usuario.

void _incrementCounter() {
    setState(() {
      print('setState()');
      _counter++;
    });
  }

didUpdateWidget()

Cuando un widget padre quiere enviar propiedades a un widget, Flutter llama al método didUpdateWidget() con el widget anterior (sin las nuevas propiedades) como argumento. Después de este proceso, Flutter siempre llama a build().

  @override
  void didUpdateWidget(covariant HomePage oldWidget) {
    print('didUpdateWidget()');
    super.didUpdateWidget(oldWidget);
  }

deactivate()

Cuando el widget padre crea un widget con un tipo de ejecución o key diferentes, Flutter llama el método deactivate(), con el fin de limpiar cualquier vínculo entre el objeto y elementos del árbol.

  @override
  void deactivate() {
    print('deactivate()');
    super.deactivate();
  }

dispose()

Cuando Flutter llama el método dispose(), se encarga de desmontarlo del árbol de widgets. Esta etapa es la última del ciclo de vida de un widget. No se puede volver a montar un State que ha pasado por el dispose().

  @override
  void dispose() {
    print('dispose()');
    super.dispose();
  }

Codigo fuente

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
      ),
      home: const HomePage(title: 'AppBar'),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key, required this.title});

  final String title;

  @override
  State<HomePage> createState() {
    print('createState()');
    return _HomePageState();
  }
}

class _HomePageState extends State<HomePage> {
  late int _counter;
  GlobalKey globalKey = GlobalKey();

  void _incrementCounter() {
    setState(() {
      print('setState()');
      _counter++;
    });
  }

  @override
  void initState() {
    print('initState()');
    print('mounted: $mounted');
    _counter = 0;

    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies()');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant HomePage oldWidget) {
    print('didUpdateWidget()');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    print('dispose()');
    super.dispose();
  }

  @override
  void deactivate() {
    print('deactivate()');
    super.deactivate();
  }

  @override
  Widget build(BuildContext context) {
    print('build()');

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        key: globalKey,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Contenido del Body:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
    );
  }
}
orden de métodos

Conclusión

En conclusión, los dos tipos de Widgets que podemos crear van a depender mucho de la necesidad que se tenga en cada punto de nuestra aplicación, pero un distintivo básico para tomar la decisión podría ser: si necesito conservar el estado (StatefulWidget) sino importa el estado (StatelessWidget).

Siguiente artículo

Veremos un ejemplo básico de cómo utilizar la navegación en Flutter.

¡¡¡Gracias por leer este artículo!!!

Referencia

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *