diegoRodicio

View Categories

⚡ Widgets Con Estado (StatefulWidget)

Tempo de lectura estimado: 8 minutos

Os StatefulWidget son aqueles widgets que poden cambiar co tempo, é dicir, teñen estado mutable. Son ideais para situacións nas que a interface debe actualizarse tras unha acción do usuario ou un evento.

❓ Cando usar StatefulWidget

Usamos StatefulWidget cando:

  • A interface precisa cambiar despois de certa acción (por exemplo, ao premer un botón).
  • Hai datos que cambian dinamicamente (como o contador dun cronómetro ou o contido dun formulario).
  • A pantalla precisa gardar estado entre actualizacións (como o scroll nunha lista).

📌 Exemplos típicos:

  • Contadores.
  • Formularios que se validan mentres se escriben.
  • Elementos interactivos como Checkbox, Slider, Switch, etc.
  • Un campo de texto (TextField) onde o usuario introduce información.
  • Un reprodutor de vídeo que mostra o tempo transcorrido.

🧬 Estrutura e ciclo de vida (initState, build, dispose)

A diferenza dos StatelessWidget que son unha única clase, un StatefulWidget componse de dúas clases:

  1. A clase principal, que estende de StatefulWidget. É inmutable.
  2. A clase State:. Aquí é onde se gardan os datos que cambian e se constrúe a interface.

🔄 Métodos principais do ciclo de vida

MétodoCando se chamaPara que serve
initState()Só unha vez, ao crear o widgetInicializar variables, abrir recursos, comezar animacións
build()Sempre que se crea ou se chama setState()Construír a interface visual do widget
dispose()Ao destruír o widgetLiberar recursos, pechar conexións ou controladores

Ciclo de vida importante:

initState()

  • Chamado unha vez ao comezar.
  • Non é obrigatorio
  • Úsase para inicializacións, como cargar datos ou realizar configuracións que só necesitan facerse unha vez.
  • Chámase unha única vez cando o obxecto State se crea e se insire na árbore de widgets.
  • Sempre debes chamar a super.initState() ao principio.

build(BuildContext context)

  • Este método é obrigatorio e chámase cada vez que o widget necesita ser debuxado ou reconstruído (por exemplo, tras un setState()).
  • Aquí definimos que widgets se deben mostrar.

dispose()

  • Chamado cando o widget se elimina da árbore.
  • Non é obrigatorio
  • Úsase para liberar recursos, como cancelación de listeners ou controladores.
  • Sempre debes chamar a super.dispose() ao final

🔗 Ver o código en DartPad

✍️ O método setState()

Cando o estado interno do teu widget cambia e queres que eses cambios se reflictan na pantalla, debes chamar a setState().

Chamando a setState(), dígolle a Flutter que algo cambiou e debe reconstruír a interface.

setState(() {
  // Actualizamos o valor dun atributo do estado
  _contador++;
});

🧪 Exemplo práctico (Contador simple)

🔗 Ver o código en DartPad

🧪 Outro contador

🔗 Ver o código en DartPad

⚡ Xeración de Código Automático (Snippets)

Lembra que en Visual Studio Code dispomos do atallo (snippet) stful, de xeito que ao escribilo o editor automaticamente crea un StatefulWidget completo coa súa clase State asociada.

🧩 Comunicación entre o StatefulWidget e a súa clase State: propiedade widget

Cando traballamos cun StatefulWidget, debemos ter sempre presente unha idea fundamental:

  • O StatefulWidget é inmutable → non se pode modificar unha vez creado.
  • A súa clase asociada, State, é mutable → é quen garda e xestiona os datos que cambian.

Isto implica que:

  • Os valores que lle pasamos polo construtor ao StatefulWidget non poden alterarse.
  • Pero a interface da app si pode cambiar, e para iso usamos o estado (State).

Para conectar ambos elementos, Flutter proporciona a propiedade especial chamada widget.

💡 Idea clave:
Os datos chegan polo widget (inmutable), pero é o estado quen os copia e os modifica. Esta separación é esencial para comprender o funcionamento de StatefulWidget.

🔍 A propiedade widget dentro do State

Dentro da clase State, existe unha propiedade chamada widget, que é unha referencia automática ao StatefulWidget pai.
Grazas a ela podemos acceder ás propiedades inmutables do widget desde o estado.

Exemplo:

class OMeuWidget extends StatefulWidget {
  final String texto;
  const OMeuWidget({super.key, required this.texto});

  @override
  State<OMeuWidget> createState() => _OMeuWidgetState();
}

class _OMeuWidgetState extends State<OMeuWidget> {
  @override
  Widget build(BuildContext context) {
    return Text(widget.texto); // ← Acceso á propiedade do widget
  }
}

🔄 Por que crear unha copia?

As propiedades dun StatefulWidget non se poden modificar directamente (son inmutables), pero moitas veces necesitamos traballar con elas:

  • editalas,
  • transformalas ou aplicarlle cambios
  • ou reaccionar a cambios na UI.

Para iso creamos variables mutables dentro do estado, que si poden cambiar co setState().

🧠 Resumo:

ElementoNaturezaFunción
StatefulWidgetInmutableRecibe valores iniciais polo construtor
StateMutableGarde e modifica datos durante a vida do widget
widgetReferenciaPermite acceder ás propiedades do widget
Variables do estadoMutablesCambian a UI mediante setState()

Exemplo:

class _OMeuWidgetState extends State<OMeuWidget> {
  late String textoEditado;

  @override
  void initState() {
    super.initState();
    textoEditado = widget.texto; // Copia editable
  }
}

Agora textoEditado pertence ao estado, e podemos modificalo libremente co setState().

🔁 Pasar datos modificables a un StatefulWidget (e recibir cambios)

É moi habitual que unha pantalla lle pase datos a outra para editalos, e despois, ao volver á pantalla anterior, queiramos recuperar os cambios.

Para iso existen dúas técnicas principais:

  • ✔️ Usando Navigator.push() para enviar datos e Navigator.pop(valor) para devolvelos.

  • ✔️ Usando callbacks, onde a pantalla pai lle pasa unha función ao fillo para que lle devolva os cambios inmediatamente.

📌 Recomendación: Antes de avanzar, asegúrate de ler o apartado dedicado aos callbacks, ao final deste tema, xa que será esencial para entender a segunda técnica.

Pasar datos mediante Navigator.push()

O proceso consta de 4 pasos fundamentais:

  • 1️⃣ O StatefulWidget recibe datos (inmutables)
  • 2️⃣ A clase State copia os datos e modifícaos
  • 3️⃣ Devolver o valor desde a segunda pantalla
  • 4️⃣ A pantalla anterior usa await Navigator.push e recibe datos de volta

1️⃣ O StatefulWidget recibe datos (inmutables)

Un StatefulWidget sempre recibe os datos a través do seu construtor.
Eses datos quedan gardados como propiedades final, porque o widget é inmutable.

No exemplo, a pantalla recibe un número enteiro chamado contador:

class PantallaContador extends StatefulWidget {
  final int contador;
  const PantallaContador({super.key, required this.contador});

  @override
  State<PantallaContador> createState() => _PantallaContadorState();
}

2️⃣ A clase State copia os datos e modifícaos

Unha vez que o StatefulWidget recibe os datos iniciais, é a clase State quen debe traballar con eles.

Lembra que as propiedades do widget son inmutables, polo que non poden modificarse directamente.

A solución consiste en crear unha copia editable dentro do estado:

  • Declaramos unha variable mutable (Neste exemplo, _contador).
  • Inicializámola en initState() tomando o valor recibido polo widget.
  • A partir deste momento, os cambios faranse sempre sobre esta copia sando setState().
class _PantallaContadorState extends State<PantallaContador> {
  late int _contador;

  @override
  void initState() {
    super.initState();
    _contador = widget.contador; // Copia editable
  }
}

Neste punto:

  • widget.contador → valor inicial, inmutable
  • _contador → copia editable que pertence ao estado

3️⃣ Devolver o valor desde a segunda pantalla

Cando estamos nun StatefulWidget que actúa como editor (por exemplo, unha pantalla onde o usuario modifica datos), é habitual que, ao rematar, queiramos volver á pantalla anterior e devolvendo os datos actualizados.

Flutter permite isto mediante Navigator.pop().
Esta función non só pecha a pantalla actual, senón que pode devolver valores á pantalla que fixo o Navigator.push().

O proceso é o seguinte:

  • O usuario preme un botón para gardar os cambios.
  • Chamamos a Navigator.pop(context, valor).
  • Ese valor é recibido pola pantalla anterior, que estará agardando cun await Navigator.push(...), tal como veremos no seguinte punto.

Así, no exemplo:

Navigator.pop(context, _contador);

4️⃣ A pantalla anterior usa await Navigator.push e recibe datos de volta

Para completar o ciclo de comunicación entre pantallas, a pantalla orixinal —a que chama ao editor— debe agardar a que a segunda pantalla devolva un valor.

Isto faise mediante await Navigator.push(...), que pausa a execución ata que a pantalla destino chame a Navigator.pop(...).

O fluxo é o seguinte:
1. A pantalla principal chama ao editor usando Navigator.push.
2. A execución detense nese punto grazas a await.
3. Cando o usuario remata na pantalla secundaria, esta usa Navigator.pop(context, valor) para devolver un dato (tal e como vimos no apartado anterior).
4. O await recibe os datos, no exemplo recibimos o valor na variable novoContador.
5. Por último, actualizamos a interface chamando a setState().

Este proceso permite que a primeira pantalla se reconstrúa automaticamente co novo dato recibido.

Exemplo aplicado ao noso contador:

...

Future<void> _abrirEditor() async {
  final novoContador = await Navigator.push<int>(
    context,
    MaterialPageRoute(
      builder: (context) => PantallaContador(contador: _contador),
    ),
  );

  if (novoContador != null) {
    setState(() {
      _contador = novoContador;
    });
  }
}

@override
  Widget build(BuildContext context) {
  ...
        ElevatedButton(
            onPressed: _abrirEditor,
            child: const Text("Ir á pantalla para incrementar o contador"));
            
    ...


Neste punto:

  • Navigator.push → abre a segunda pantalla
  • await → queda esperando un resultado
  • Navigator.pop(_, valor) (na pantalla secundaria) → envía o valor de volta
  • setState() → actualiza a UI coa nova información

📝 Exemplo completo:

🔗 Ver o código en DartPad

🧠 Resumo conceptual

ElementoNaturezaPara que serve
StatefulWidgetInmutableRecibe os datos iniciais polo construtor. Non cambia durante a execución.
StateMutableGarde e modifica os datos que cambian coa interacción do usuario.
widgetReferencia ao StatefulWidgetPermite acceder ás propiedades inmutables que chegaron polo widget.
Variables do EstadoMutablesCopias editables dos datos, modificables mediante setState().
Navigator.push()NavegaciónAbre unha nova pantalla e pode devolver un valor á anterior.
Navigator.pop(valor)Pechar + devolver datosPecha a pantalla actual e devolve valor á pantalla chamante.

📌 Idea clave:

  • O StatefulWidget recibe datos (non se modifican).
  • O State copia e modifica (si pode cambiar).
  • A actualización da UI sempre vai con setState().
  • A pantalla anterior pode recibir datos mediante Navigator.

📚 Onde atopar máis información