WatchingWidgets
¿Por Qué Necesitas Widgets Especiales?
Podrías preguntarte: "¿Por qué no puedo simplemente usar watchValue() en un StatelessWidget regular?"
El problema: watch_it necesita engancharse al ciclo de vida de tu widget para:
- Suscribirse a cambios cuando el widget se construye
- Desuscribirse cuando el widget se dispone (prevenir memory leaks)
- Reconstruir el widget cuando los datos cambian
Un StatelessWidget regular no le da a watch_it acceso a estos eventos del ciclo de vida. Necesitas un widget al que watch_it pueda engancharse.
WatchingWidget - Para Widgets Sin Estado Local
Reemplaza StatelessWidget con WatchingWidget:
class TodoList extends WatchingWidget {
@override
Widget build(BuildContext context) {
final todos = watchValue((TodoManager m) => m.todos);
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) => Text(todos[index].title),
);
}
}Usa esto cuando:
- Escribes nuevos widgets
- No necesitas estado local (
setState) - UI reactiva simple
WatchingStatefulWidget - Para Widgets Con Estado Local
Usa cuando necesites tanto setState COMO estado reactivo:
class TodoListWithFilter extends WatchingStatefulWidget {
@override
State createState() => _TodoListWithFilterState();
}
class _TodoListWithFilterState extends State<TodoListWithFilter> {
bool _showCompleted = true; // Local UI state
@override
Widget build(BuildContext context) {
// Reactive state - rebuilds when todos change
final todos = watchValue((TodoManager m) => m.todos);
// Filter based on local state
final filtered = _showCompleted
? todos
: todos.where((todo) => !todo.completed).toList();
return Column(
children: [
SwitchListTile(
title: Text('Show completed'),
value: _showCompleted,
onChanged: (value) => setState(() => _showCompleted = value),
),
Expanded(
child: ListView.builder(
itemCount: filtered.length,
itemBuilder: (context, index) => CheckboxListTile(
title: Text(filtered[index].title),
value: filtered[index].completed,
onChanged: (_) => di<TodoManager>().updateTodoCommand.run(
filtered[index]
.copyWith(completed: !filtered[index].completed)),
),
),
),
],
);
}
}Usa esto cuando:
- Necesitas estado de UI local (toggles de filtro, estado de expansión)
- Mezcla
setStatecon actualizaciones reactivas
Nota: ¡Tu clase State automáticamente obtiene todas las funciones watch - no se necesita mixin!
Patrón: Estado local (_showCompleted) para preferencias solo de UI, estado reactivo (todos) del manager, y checkboxes llaman de vuelta al manager para actualizar datos.
⚠️ Importante: Con
watch_it, raramente necesitarás StatefulWidget más. La mayoría del estado pertenece en tus managers y se accede reactivamente. InclusoTextEditingControlleryAnimationControllerpueden crearse concreateOnce()enWatchingWidget- ¡no se necesita StatefulWidget! Solo usa StatefulWidget para estado de UI verdaderamente local que requieresetState.
Alternativa: Usar Mixins
Si tienes widgets existentes que no quieres cambiar, usa mixins en su lugar:
Para StatelessWidget Existente
class TodoListWithMixin extends StatelessWidget with WatchItMixin {
const TodoListWithMixin({super.key}); // Can use const!
@override
Widget build(BuildContext context) {
final todos = watchValue((TodoManager m) => m.todos);
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) => Text(todos[index].title),
);
}
}Para StatefulWidget Existente
class TodoListWithFilterMixin extends StatefulWidget
with WatchItStatefulWidgetMixin {
const TodoListWithFilterMixin({super.key});
@override
State createState() => _TodoListWithFilterMixinState();
}
class _TodoListWithFilterMixinState extends State<TodoListWithFilterMixin> {
bool _showCompleted = true;
@override
Widget build(BuildContext context) {
final todos = watchValue((TodoManager m) => m.todos);
final filtered = _showCompleted
? todos
: todos.where((todo) => !todo.completed).toList();
return Column(
children: [
SwitchListTile(
title: Text('Show completed'),
value: _showCompleted,
onChanged: (value) => setState(() => _showCompleted = value),
),
Expanded(
child: ListView.builder(
itemCount: filtered.length,
itemBuilder: (context, index) => CheckboxListTile(
title: Text(filtered[index].title),
value: filtered[index].completed,
onChanged: (_) => di<TodoManager>().updateTodoCommand.run(
filtered[index]
.copyWith(completed: !filtered[index].completed)),
),
),
),
],
);
}
}¿Por qué usar mixins?
- Mantener jerarquía de clases existente
- Puede usar constructores
constconWatchItMixin - Cambios mínimos al código existente
- Perfecto para migración gradual
Guía de Decisión Rápida
¿Nuevo widget, sin estado local? ✅ Usa WatchingWidget
¿Nuevo widget CON estado local? ✅ Usa WatchingStatefulWidget
¿Migrando StatelessWidget existente? ✅ Añade with WatchItMixin
¿Migrando StatefulWidget existente? ✅ Añade with WatchItStatefulWidgetMixin al StatefulWidget (¡no al State!)
Patrones Comunes
Combinar con Otros Mixins
class AnimatedCard extends WatchingStatefulWidget {
@override
State createState() => _AnimatedCardState();
}
class _AnimatedCardState extends State<AnimatedCard>
with SingleTickerProviderStateMixin {
// Mix with other mixins!
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
}
@override
Widget build(BuildContext context) {
final data = watchValue((DataManager m) => m.data);
return AnimatedBuilder(
animation: _controller,
builder: (context, child) => Transform.scale(
scale: _controller.value,
child: Text(data),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}Ver También
- Getting Started - Uso básico de
watch_it - Your First Watch Functions - Aprende
watchValue() - Watch Ordering Rules - Reglas CRÍTICAS