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:
// 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():
// 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:
// 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:
// 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:
// 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 GetItCausa: Intentar observar un objeto que no ha sido registrado en get_it.
Solución: Regístralo antes de usarlo:
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
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();
}
}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
// 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):
// 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:
// 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:
// 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();
}
}// 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
// 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:
// 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:
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:
// 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 = trueglobalmente para que los controles de subárbol funcionen
Aislar el problema
Crea reproducción mínima:
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:
- Reproducción mínima - Aísla el problema
- Versiones - Versiones de
watch_it, Flutter, Dart - Mensajes de error - Stack trace completo
- Esperado vs actual - Qué debería suceder vs qué sucede
- Muestra de código - Ejemplo completo y ejecutable
Dónde preguntar:
- Discord: Únete a la comunidad flutter_it
- GitHub Issues: issues de watch_it
- Stack Overflow: Etiqueta con
flutterywatch-it
Ver También
- Watch Ordering Rules - Restricciones CRÍTICAS
- Best Practices - Patrones y tips
- How watch_it Works - Entender el mecanismo