← Flutter & Dart

Flutter avanzado

¿Qué causa que un widget se reconstruya y cómo evitas reconstrucciones innecesarias?

Ver respuesta — intenta responderla en voz alta primero

Un widget se reconstruye cuando su padre se reconstruye, cuando llamas a setState, o cuando cambia un InheritedWidget del que depende. Para evitar reconstrucciones innecesarias: usa constructores const, extrae subárboles a widgets separados, acota el alcance de setState y usa el hijo (child) que no se reconstruye en widgets como AnimatedBuilder.

Reconstruir (build) no pinta pixeles directamente; genera la descripción del árbol. Pero si reconstruyes mucho y de más, gastas CPU y puedes causar jank (saltos en la animación). Las causas de un rebuild:

  1. El padre se reconstruye: por defecto arrastra a sus hijos.
  2. setState: reconstruye el State que lo llamó.
  3. Cambia un InheritedWidget del que el widget depende (por ejemplo, Theme).

Estrategias para reducirlos:

  • const: subárboles que no cambian no se reconstruyen.
  • Extraer widgets: mueve la parte que cambia a su propio widget pequeño, para que solo ese se reconstruya.
  • Acotar setState: que el setState viva en el widget más pequeño posible, no en la pantalla entera.
  • Parámetro child: en AnimatedBuilder/ValueListenableBuilder, el child se construye una vez y se pasa sin reconstruirse.
import 'package:flutter/material.dart';

// El truco del "child": la parte cara (el Text) se construye UNA vez
// y no se reconstruye en cada frame de la animación.
class Pulsante extends StatelessWidget {
  const Pulsante({super.key, required this.animacion});
  final Animation<double> animacion;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animacion,
      // Este child NO se reconstruye en cada frame:
      child: const Text("No me reconstruyo cada frame"),
      builder: (context, child) {
        return Opacity(
          opacity: animacion.value,
          child: child, // reutilizo el child construido una sola vez
        );
      },
    );
  }
}

Llamar a setState en el widget más alto (por ejemplo, en toda la pantalla) por un cambio que solo afecta a un botoncito. Eso reconstruye media UI cuando bastaría con reconstruir ese botón. También es común no usar const y provocar reconstrucciones evitables en cada frame. Para diagnosticar, se usan las Flutter DevTools (el Performance y el rebuild counter).

"Un widget se reconstruye cuando su padre lo hace, cuando llamo a setState, o cuando cambia un InheritedWidget del que depende, como el Theme. Para evitar rebuilds innecesarios uso constructores const, extraigo la parte que cambia a su propio widget pequeño para acotar el rebuild, pongo el setState en el widget más específico posible, y aprovecho el parámetro child de AnimatedBuilder para no reconstruir lo que no cambia. Cuando algo va lento, lo mido con las DevTools antes de optimizar a ciegas."

Reto rápido

¿Para qué sirve el parámetro child en un AnimatedBuilder?

Ver respuesta

Sirve para construir una sola vez la parte del subárbol que no depende de la animación, y reutilizarla en cada frame. El builder se ejecuta en cada frame de la animación, pero el child se pasa ya construido, así que evitas reconstruir esa porción 60 veces por segundo. Es una optimización clave en animaciones.