Flutter StatelessWidget vs StatefulWidget: comprensión profunda

Entender la diferencia entre StatelessWidget y StatefulWidget es la base del desarrollo con Flutter.

Esta sección analizará en profundidad los escenarios de uso y el mecanismo de funcionamiento interno de estos dos tipos de Widget.


Diferencia central

En Flutter hay dos tipos principales de Widgets; su diferencia fundamental radica en si pueden mantener un estado mutable.

CaracterísticaStatelessWidgetStatefulWidget
EstadoInmutableMutable
Momento de reconstrucciónSolo cuando el Widget padre se reconstruyeCuando el estado cambia + cuando el Widget padre se reconstruye
RendimientoMayor (sin necesidad de escuchar el estado)Ligeramente menor (requiere gestión de estado)
Escenarios de usoContenido estáticoContenido dinámico interactivo

Explicación detallada de StatelessWidget

StatelessWidget es adecuado para contenido que no cambia con el tiempo. Una vez creado, todas sus propiedades son final y no se pueden modificar.

Escenarios de uso

  • Mostrar texto, imágenes e iconos estáticos
  • Mostrar listas de datos (la fuente de datos proviene del Widget padre)
  • Contenedores de diseño que no incluyen interacción del usuario
  • La porción de solo lectura en formularios

Ejemplo: ejemplo completo de StatelessWidget

import 'package:flutter/material.dart';

// Componente de visualización de información de usuario (sin estado)
class UserCard extends StatelessWidget {
  // Las propiedades se definen como final para garantizar la inmutabilidad
  final String name;
  final String email;
  final String avatarUrl;

  // El constructor const mejora el rendimiento
  const UserCard({
    super.key,
    required this.name,
    required this.email,
    this.avatarUrl = '',
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Row(
          children: [
            // Avatar
            CircleAvatar(
              backgroundImage: avatarUrl.isNotEmpty
                  ? NetworkImage(avatarUrl)
                  : null,
              child: avatarUrl.isEmpty
                  ? const Icon(Icons.person)
                  : null,
            ),
            const SizedBox(width: 16),
            // Información del usuario
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    name,
                    style: const TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    email,
                    style: TextStyle(
                      color: Colors.grey[600],
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Explicación detallada de StatefulWidget

StatefulWidget es adecuado para escenarios que necesitan responder a interacciones del usuario o cambios de datos. Se compone de dos partes: el propio Widget (inmutable) y el objeto State (mutable).

Escenarios de uso

  • Datos dinámicos como contadores, temporizadores, etc.
  • Entrada de formularios
  • Control de animaciones
  • Visualización del estado de solicitudes de red
  • Respuesta a la interacción del usuario

Ciclo de vida del objeto State

MétodoDescripción
initStateSe llama cuando el Widget se crea por primera vez, para inicializar el estado.
didChangeDependenciesSe llama cuando cambian las dependencias del Widget.
buildConstruye la UI; se llama cada vez que cambia el estado.
didUpdateWidgetSe llama cuando el Widget padre se reconstruye.
disposeSe llama cuando el Widget se elimina, para limpiar recursos.

Ejemplo: ejemplo completo de StatefulWidget

import 'package:flutter/material.dart';

// Aplicación de contador
class CounterApp extends StatefulWidget {
  const CounterApp({super.key});

  @override
  State<CounterApp> createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  // Estado del contador
  int _counter = 0;
  // Temporizador (para demostrar dispose)
  Timer? _timer;

  // 1. initState - inicializar el estado
  @override
  void initState() {
    super.initState();
    // Inicializar el contador
    _counter = 0;
    // Iniciar un temporizador (incrementa una vez por segundo)
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      _increment();
    });
  }

  // 2. didChangeDependencies - se llama cuando cambian las dependencias
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
  }

  // 3. build - construir la UI
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Contador'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Conteo actual:',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '$_counter',
              style: const TextStyle(
                fontSize: 48,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
      // Botón de incremento
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: const Icon(Icons.add),
      ),
    );
  }

  // Método para incrementar el contador
  void _increment() {
    // Se debe llamar a setState para desencadenar la actualización de la UI
    setState(() {
      _counter++;
    });
  }

  // 4. didUpdateWidget - se llama cuando el Widget padre se reconstruye
  @override
  void didUpdateWidget(CounterApp oldWidget) {
    super.didUpdateWidget(oldWidget);
  }

  // 5. dispose - limpiar recursos
  @override
  void dispose() {
    // Cancelar el temporizador para evitar fugas de memoria
    _timer?.cancel();
    super.dispose();
  }
}

Uso correcto de setState

En StatefulWidget, es necesario notificar a Flutter que el estado ha cambiado mediante setState.

Uso correcto

Ejemplo: uso correcto de setState

// Correcto: actualizar el estado dentro de setState
void _increment() {
  setState(() {
    _counter++;
  });
}

// Correcto: si la actualización del estado no está relacionada con la UI, no es necesario usar setState
void _someInternalLogic() {
  // Lógica interna, no afecta a la UI
  _calculateSomething();
}

// Correcto: actualizar el estado después de una operación asíncrona
Future<void> _loadData() async {
  final data = await fetchData();
  setState(() {
    _items = data;
  });
}

Uso incorrecto

Ejemplo: uso incorrecto de setState

// Incorrecto: modificar el estado sin llamar a setState
void _increment() {
  _counter++;  // El estado ha cambiado, ¡pero la UI no se actualizará!
}

// Incorrecto: llamar a setState dentro del método build
@override
Widget build(BuildContext context) {
  // ¡Nunca llames a setState en build!
  setState(() {
    _counter++;  // Esto causará un bucle infinito
  });
  return Text('$_counter');
}

setState hará que se vuelva a llamar al método build en el siguiente fotograma.

Llamar a setState con frecuencia o hacerlo dentro del método build puede provocar problemas de rendimiento.


Cómo elegir

Al desarrollar, se debe elegir el tipo de Widget adecuado según las necesidades reales:

Elija StatelessWidget cuando:

  • La apariencia del Widget no depende de ningún estado mutable
  • Todos los datos necesarios se pasan desde el Widget padre mediante el constructor
  • El Widget no necesita responder a las interacciones del usuario
  • Se busca el mejor rendimiento

Elija StatefulWidget cuando:

  • El Widget necesita mantener un estado interno
  • Se necesita responder a la entrada del usuario (clics, deslizamientos, etc.)
  • Se necesita responder a cambios de datos (como el retorno de una solicitud de red)
  • Se necesita controlar animaciones
Otras extensiones