> For the complete documentation index, see [llms.txt](https://ecm-pmdm-flutter.gitbook.io/1.-introduccion-a-flutter/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ecm-pmdm-flutter.gitbook.io/1.-introduccion-a-flutter/4.-conceptos-fundamentales-en-flutter/widgets/stateful-widget.md).

# Stateful widget

Un widget **stateful** extiende de la clase **`StatefulWidget`**. Contiene datos que pueden **cambiar** en tiempo de ejecución y que afectarán a la **visualización** de la UI, que deberá ser actualizada debidamente. &#x20;

Los **StatefulWidgets** resultan útiles cuando la parte de la UI que estamos describiendo puede cambiar.   Por ejemplo: el usuario pulsa un botón y el texto de un widget Text debe actualizarse.

El **estado** del widget viene dado por sus propiedades (datos).

## Implementación de un StatefulWidget

Para **implementar** un S*tateful widget*, necesitamos definir dos clases:

* Una clase que extiende de la clase **`StatefulWidget`** que creará una instancia de la clase `State.`
* Una clase que extiende de **`State`** que contiene:
  * El **estado&#x20;*****mutable*** para este widget&#x20;
  * El método **`build()`**

{% hint style="success" %}
Desde el objeto **`State`** podremos acceder a BuildContext directamente con la **propiedad** ***`context`***, así como a su widget asociado con la **propiedad** ***`widget`***.
{% endhint %}

### ¿Cómo actualizar el estado del widget y hacerlo visible en pantalla?

Cada vez que  **actualicemos** las propiedades del widget, es decir, su **estado**, deberemos llamar al método **`setState(..)`**  que indica al framework de Flutter que debe **redibujar** de nuevo el widget.

`setState(..)` provocará que el framework de Flutter llame de nuevo al método **`build()`** de este objeto `State` el cual devolverá un **nuevo** **widget** con los datos **actualizados** (**nuevo** **estado**) y que ya podremos ver en pantalla.&#x20;

## **Ejemplo 1:  Widget con estado para mostrar un contador**&#x20;

Vamos a crear una aplicación `MaterialApp` compuesta por un **widget** **con** **estado** que contiene un **botón**, que ocupará toda la pantalla,  y que **muestra** un texto con el **valor** **actual** de **un** **contador**. &#x20;

**Funcionamiento**:&#x20;

* Al pulsar sobre el botón se incrementará el valor del contador. &#x20;
* Pero, si queremos ver en pantalla el contador actualizado,  se debe **ejecutar** el método **`setState(...)`** que **provoca que el framework de Flutter renderize de nuevo el botón** (se llamará de nuevo a build()) mostrando así en pantalla el nuevo valor del contador.

<figure><img src="/files/wXxqTYSao7CNrJGw9ZDG" alt="" width="188"><figcaption></figcaption></figure>

El **código** sería:

```dart
import 'package:flutter/material.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Homepage(),
    );
  }
}
class Homepage extends StatefulWidget {
  @override
  State<Homepage> createState() => _HomepageState();
}

class _HomepageState extends State<Homepage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      child: Text('Pressed: $_count'),
      onPressed: _increaseCounter,   // o directamente: () => setState(() => count++),
    );
  }
  void _increaseCounter() => setState(() => _count++);
}
```

### **Explicación del código**:

* **`State<Homepage>:`** La clase *state* extiende de `State<T>` con `T` siendo del tipo del correspondiente `StatefulWidget`.  La clase `state` creada es exclusiva para `T` y no se puede reutilizar en otro  `StatefulWidget`

{% hint style="danger" %}
El propio widget `Stateful` es **inmutable**, es su objeto `State` el que persiste durante el tiempo de vida del widget.
{% endhint %}

* Cuando queremos cambiar el estado del widget, debemos llamar a ***setState**():*  indicamos así al framework que redibuje (re-render) el widget, se llamará al **`build`** de este widget (y el de todos los widgets del árbol que se puedan ver implicados).

> **setState(...)**:  toma como argumento una función del tipo:  `VoidCallback`
>
> Un widget stateless puede contener widgets stateful y viceversa.

* **`build():`** devuelve un `ElevatedButton` que tiene asignado en `onPressed` el método  *\_increaseCounter* que se llamará al pulsarlo.    *\_increaseCounter* se encargará de llamar a setState() desde donde se incrementa el contador.

### ¿Por qué se necesita un objeto *State* separado para un *StatefulWidget*?

La razón es porque en Flutter los objetos State, **tienen** **ciclos** **de** **vida** **diferentes**:

* Los `Widgets` son objetos **temporales**, utilizados para construir una visualización (UI) de la aplicación en su estado actual.   &#x20;
* Los objetos `State`, por otra parte, son **persistentes** entre diferentes llamadas a `build()`,  esta es la forma para que los widgets pueden recordar la información (**mantienen su estado entre llamadas a build()**).

> En aplicaciones más complejas, cada widget de la jerarquía tiene unas responsabilidades concretas; por ejemplo, un widget podría representar la interfaz de usuario para recoger una información específica (por ejemplo, fecha y lugar) mientras que otro widget se encargaria de usar esa información para cambiar la visualización de la UI.

En Flutter, las **notificaciones** **de** **cambio** **fluyen** **hacia** "**arriba**" de la jerarquía de widgets,  mientras que la **visualización** **del** **estado** **actual** **fluye** hacia "**abajo**", hacia los widgets stateless que se encargan de visualizar la UI (en el estado actual que tenga el widget).   El objeto común que redirige este flujo es el **`State`**.&#x20;

## **Ejemplo 2: Flujos de notificación y de actualización** &#x20;

Veamos un ejemplo más complejo del **contador** para comprender cómo funcionan los flujos de notificación y de visualización (re-rendering).  &#x20;

<figure><img src="/files/DUVcxpTwxiRBDvLFulVh" alt="" width="188"><figcaption></figcaption></figure>

La **aplicación** **se** **compone** de los siguientes widgets:

* Dos widgets *stateless*, cada uno cumple su función, separando responsabilidades:&#x20;
  * `CounterDisplay` :  Muestra un texto con el valor del contador: &#x20;
  * `CounterIncrementor` :  Contiene un botón, que al pulsarlo, llamará a una función (callback) que incrementará el valor del contador.
* El widget *stateful* `Counter` se compone de los dos widgets stateless anteriores y contiene el estado:  el valor del contador (que se incrementará al pulsar el botón).

<mark style="color:orange;">El</mark> <mark style="color:orange;"></mark><mark style="color:orange;">**resultado**</mark> <mark style="color:orange;"></mark><mark style="color:orange;">es similar al Ejemplo 1  pero al</mark> <mark style="color:orange;"></mark><mark style="color:orange;">**separar**</mark> <mark style="color:orange;">**responsabilidades**</mark> <mark style="color:orange;"></mark><mark style="color:orange;">se permite que la</mark> <mark style="color:orange;"></mark><mark style="color:orange;">**complejidad**</mark> <mark style="color:orange;"></mark><mark style="color:orange;">se</mark> <mark style="color:orange;"></mark><mark style="color:orange;">**encapsule**</mark> <mark style="color:orange;"></mark><mark style="color:orange;">en los widgets individuales a la vez que se mantiene la</mark> <mark style="color:orange;"></mark><mark style="color:orange;">**simplicidad**</mark> <mark style="color:orange;"></mark><mark style="color:orange;">en el widget padre</mark> <mark style="color:orange;"></mark><mark style="color:orange;">`Counter`</mark><mark style="color:orange;">.</mark>

### Veamos el **código**:

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Counter(),
        ),
      ),
    ),
  );
}

class CounterDisplay extends StatelessWidget {
  const CounterDisplay({required this.count, Key? key}) : super(key: key);

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  const CounterIncrementor({required this.onPressed, Key? key}) : super(key: key);

  final VoidCallback onPressed;  // VoidCallback type: no args and no returned data

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: const Text('Increase Counter'),
    );
  }
}

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

  @override
  State<Counter>  createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  // setState(..): makes that framework calls build()
  void _increment() => setState(() => ++_counter);  

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        CounterDisplay(count: _counter),
        const SizedBox(height: 16, ),    // to make same room between widgets
        CounterIncrementor(onPressed: _increment),   // set callback
      ],
    );
  }
}
```

#### <mark style="color:blue;">**Actividad**</mark><mark style="color:blue;">: Añade la siguiente funcionalidad a la aplicación contador:</mark> &#x20;

<details>

<summary><mark style="color:blue;">Reset del contador.....</mark></summary>

* El usuario ha de poder resetear el contador al pulsar un botón.  El contador deberá mostrar el valor 0 e incrementarse a partir de este valor.

</details>

### Contadores independientes

A partir de nuestro statefulwidget `Counter`, podríamos visualizar fácilmente **varios** **contadores** **independientes**, cada uno con su propio objeto State:

{% tabs %}
{% tab title="Ejemplo con múltiples contadores" %}

<figure><img src="/files/4YwBT6QAn1nFsyOpmtpm" alt="" width="188"><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Código" %}

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: Scaffold(
      body: const MultipleCounters(),
    ),
  ));
}

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

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Counter(), 
              SizedBox(height: 20), 
              Counter(), 
              SizedBox(height: 20,), 
              Counter(),
            ],
    ));
  }
}

// same code:
class CounterDisplay extends StatelessWidget {
  const CounterDisplay({required this.count, Key? key}) : super(key: key);

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  const CounterIncrementor({required this.onPressed, Key? key})
      : super(key: key);

  final VoidCallback
      onPressed; // VoidCallback type: no args and no returned data

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: const Text('Increase Counter'),
    );
  }
}

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

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() => setState(
      () => ++_counter); // setState(..): makes that framework calls build()

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        CounterDisplay(count: _counter),
        const SizedBox(
          height: 16,
        ), // to make same room between widgets
        CounterIncrementor(onPressed: _increment), // set callback
      ],
    );
  }
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ecm-pmdm-flutter.gitbook.io/1.-introduccion-a-flutter/4.-conceptos-fundamentales-en-flutter/widgets/stateful-widget.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
