Skip to content

Debugging y Resolución de Problemas

Errores comunes, soluciones, técnicas de debugging y estrategias de resolución de problemas para watch_it.

Errores Comunes

"Watch ordering violation detected!"

Mensaje de error:

Watch ordering violation detected!

You have conditional watch calls (inside if/switch statements) that are
causing `watch_it` to retrieve the wrong objects on rebuild.

Fix: Move ALL conditional watch calls to the END of your build method.
Only the LAST watch call can be conditional.

Causa: Llamadas watch dentro de declaraciones if seguidas por otros watches, causando que el orden cambie entre construcciones.

Solución: Ver Watch Ordering Rules para explicación detallada, ejemplos y patrones seguros.

Tip de debugging: Llama a enableTracing() en tu método build para ver las ubicaciones exactas de fuente de las declaraciones watch en conflicto.

"watch() called outside build"

Mensaje de error:

watch() can only be called inside build()

Causa: Intentar usar funciones watch en callbacks, constructores u otros métodos.

Ejemplo:

dart
// BAD
class WatchOutsideBuildBad extends WatchingWidget {
  WatchOutsideBuildBad() {
    final data = watchValue((Manager m) => m.data); // Wrong context!
  }

  void onPressed() {
    final data = watchValue((Manager m) => m.data); // Wrong!
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Solución: Solo llama funciones watch directamente en build():

dart
// GOOD
class WatchOutsideBuildGood extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    final data = watchValue((Manager m) => m.data); // Correct!

    return ElevatedButton(
      onPressed: () {
        doSomething(data); // Use the value
      },
      child: Text('$data'),
    );
  }
}

void doSomething(String data) {}

"Type 'X' is not a subtype of type 'Listenable'"

Mensaje de error:

type 'MyManager' is not a subtype of type 'Listenable'

Causa: Usar watchIt<T>() en un objeto que no es un Listenable.

Ejemplo:

dart
// BAD
class TodoManagerNotListenable {
  // Not a Listenable!
  final todos = ValueNotifier<List<Todo>>([]);
}

class NotListenableBad extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // ignore: type_argument_not_matching_bounds
    final manager = watchIt<TodoManagerNotListenable>(); // ERROR!
    return Container();
  }
}

Solución: Usa watchValue() en su lugar:

dart
// GOOD
class NotListenableGoodWatchValue extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    final todos = watchValue((TodoManagerNotListenable m) => m.todos);
    return Container();
  }
}

O haz que tu manager extienda ChangeNotifier:

dart
// Also GOOD
class TodoManagerListenable extends ChangeNotifier {
  List<Todo> _todos = [];

  void addTodo(Todo todo) {
    _todos.add(todo);
    notifyListeners(); // Now it's a Listenable
  }
}

class NotListenableGoodChangeNotifier extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    final manager = watchIt<TodoManagerListenable>(); // Works now!
    return Container();
  }
}

"get_it: Object/factory with type X is not registered"

Mensaje de error:

get_it: Object/factory with type TodoManager is not registered inside GetIt

Causa: Intentar observar un objeto que no ha sido registrado en get_it.

Solución: Regístralo antes de usarlo:

dart
void main() {
  // Register BEFORE runApp
  di.registerSingleton<TodoManager>(TodoManager(DataService(ApiClient())));

  runApp(MyApp());
}

Ver Registro de Objetos en get_it para todos los métodos de registro.

El widget no se reconstruye cuando los datos cambian

Síntomas:

  • Los datos cambian pero la UI no se actualiza
  • print() muestra nuevos valores pero el widget todavía muestra datos antiguos

Causas comunes:

1. No observar los datos

dart
class NotWatchingBad extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // BAD - Not watching, just accessing
    final manager = di<TodoManager>();
    final todos = manager.todos.value; // No watch!
    return Container();
  }
}
dart
class NotWatchingGood extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // GOOD - Actually watching
    final todos = watchValue((TodoManager m) => m.todos);
    return Container();
  }
}

2. No notificar cambios

dart
// BAD - Changing value without notifying
class TodoManagerNotNotifying {
  final todos = ValueNotifier<List<Todo>>([]);

  void addTodo(Todo todo) {
    todos.value.add(todo); // Modifies list but doesn't notify!
  }
}

Opción 1 - Usar ListNotifier de listen_it (recomendado):

dart
// GOOD - ListNotifier automatically notifies on mutations
class TodoManagerListNotifier {
  final todos = ListNotifier<Todo>(data: []);

  void addTodo(Todo todo) {
    todos.add(todo); // Automatically notifies listeners!
  }
}

Opción 2 - Usar ValueNotifier personalizado con notificación manual:

dart
// GOOD - Extend ValueNotifier and call notifyListeners
class TodoManagerCustomNotifier extends ValueNotifier<List<Todo>> {
  TodoManagerCustomNotifier() : super([]);

  void addTodo(Todo todo) {
    value.add(todo);
    notifyListeners(); // Manually trigger notification
  }
}

Ver Colecciones de listen_it para ListNotifier, MapNotifier y SetNotifier.

Memory leaks - suscripciones no limpiadas

Síntomas:

  • El uso de memoria crece con el tiempo
  • Widgets antiguos todavía reaccionando a cambios
  • El rendimiento se degrada

Causa: No usar WatchingWidget o WatchItMixin - hacer suscripciones manuales.

Solución: Siempre usa widgets watch_it:

dart
// BAD - Manual subscriptions leak
class MemoryLeakBad extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final manager = di<Manager>();
    manager.data.addListener(() {
      // This leaks! No cleanup
    });
    return Container();
  }
}
dart
// GOOD - Automatic cleanup
class MemoryLeakGood extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    final data = watchValue((Manager m) => m.data);
    return Text('$data');
  }
}

registerHandler no se dispara

Síntomas:

  • El callback del handler nunca se ejecuta
  • Los efectos secundarios (navegación, diálogos) no suceden
  • No se lanzan errores

Causas comunes:

1. Handler registrado después de return condicional

dart
// BAD - Handler registered AFTER early return
class HandlerAfterReturnBad extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    final isLoading = watchValue((SaveManager m) => m.isLoading);

    if (isLoading) {
      return CircularProgressIndicator(); // Returns early!
    }

    // This handler never gets registered when loading!
    registerHandler(
      select: (SaveManager m) => m.saveCommand,
      handler: (context, result, cancel) {
        Navigator.pop(context);
      },
    );

    return MyForm();
  }
}

Solución: Registra handlers ANTES de cualquier return condicional:

dart
// GOOD - Handler registered before conditional logic
class HandlerBeforeReturnGood extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    registerHandler(
      select: (SaveManager m) => m.saveCommand,
      handler: (context, result, cancel) {
        Navigator.pop(context);
      },
    );

    final isLoading = watchValue((SaveManager m) => m.isLoading);

    if (isLoading) {
      return CircularProgressIndicator();
    }

    return MyForm();
  }
}

Técnicas de Debugging

Habilitar Tracing de watch_it

Obtén logs detallados de suscripciones watch y ubicaciones de fuente para violaciones de ordenamiento:

dart
class MyWidget extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // Llama al inicio del build para habilitar tracing
    enableTracing(
      logRebuilds: true,
      logHandlers: true,
      logHelperFunctions: true,
    );

    final todos = watchValue((TodoManager m) => m.todos);
    // ... resto del build
  }
}

Beneficios:

  • Muestra qué watch disparó la reconstrucción de tu widget
  • Muestra ubicaciones exactas de fuente de las llamadas watch
  • Ayuda a identificar violaciones de ordenamiento
  • Rastrea actividad de reconstrucción
  • Muestra ejecuciones de handler

Caso de uso: Cuando tu widget se reconstruye inesperadamente, habilita tracing para ver exactamente qué valor observado cambió y disparó la reconstrucción. Esto te ayuda a identificar si estás observando demasiados datos o las propiedades incorrectas.

Alternativa: Usa el widget WatchItSubTreeTraceControl para habilitar tracing para un subárbol específico:

dart
// Primero, habilita subtree tracing globalmente (típicamente en main())
enableSubTreeTracing = true;

// Luego envuelve SOLO el widget/pantalla problemático - ¡NO toda la app!
// De lo contrario te ahogarás en logs de cada widget
return Scaffold(
  body: WatchItSubTreeTraceControl(
    logRebuilds: true,        // Requerido: registrar eventos de reconstrucción
    logHandlers: true,        // Requerido: registrar ejecuciones de handler
    logHelperFunctions: true, // Requerido: registrar llamadas de función helper
    child: ProblematicWidget(), // Solo el widget que estás debuggeando
  ),
);

Importante: Envuelve solo el widget o pantalla específica que causa problemas, no toda tu app. Hacer tracing de toda la app genera cantidades abrumadoras de logs.

Nota:

  • Puedes anidar múltiples widgets WatchItSubTreeTraceControl - se aplican las configuraciones del ancestro más cercano
  • Debes establecer enableSubTreeTracing = true globalmente para que los controles de subárbol funcionen

Aislar el problema

Crea reproducción mínima:

dart
class IsolateProblem extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // Minimal example - just the watch and rebuild
    final value = watchValue((CounterManager m) => m.count);

    print('Rebuild with value: $value');

    return Text('Value: $value');
  }
}

Esto aísla:

  • ¿Funciona la suscripción watch?
  • ¿Se reconstruye el widget en cambio de datos?
  • ¿Hay problemas de ordenamiento?

Obtener Ayuda

Al reportar problemas:

  1. Reproducción mínima - Aísla el problema
  2. Versiones - Versiones de watch_it, Flutter, Dart
  3. Mensajes de error - Stack trace completo
  4. Esperado vs actual - Qué debería suceder vs qué sucede
  5. Muestra de código - Ejemplo completo y ejecutable

Dónde preguntar:

Ver También

Publicado bajo la Licencia MIT.