Tus Primeras Funciones Watch
Las funciones watch son el núcleo de watch_it - hacen que tus widgets se reconstruyan automáticamente cuando los datos cambian. Empecemos con la más común.
El Watch Más Simple: watchValue
La forma más común de observar datos es con watchValue(). Observa una propiedad ValueListenable de un objeto registrado en get_it.
Ejemplo Básico de Contador
// 1. Create a manager with reactive state
class CounterManager {
final count = ValueNotifier<int>(0);
void increment() => count.value++;
}
// 2. Register it in get_it
void setupCounter() {
di.registerSingleton<CounterManager>(CounterManager());
}
// 3. Watch it in your widget
class CounterWidget extends WatchingWidget {
const CounterWidget({super.key});
@override
Widget build(BuildContext context) {
// This one line makes it reactive!
final count = watchValue((CounterManager m) => m.count);
return Scaffold(
body: Center(
child: Text('Count: $count', style: TextStyle(fontSize: 48)),
),
floatingActionButton: FloatingActionButton(
onPressed: () => di<CounterManager>().increment(),
child: Icon(Icons.add),
),
);
}
}Qué sucede:
watchValue()accede aCounterManagerdesdeget_it- Observa la propiedad
count - El widget se reconstruye automáticamente cuando count cambia
- Sin listeners manuales, sin limpieza necesaria
Magia de Inferencia de Tipos
Observa cómo especificamos el tipo del objeto padre en la función selectora:
(CounterManager m) => m.count
Al declarar el tipo del objeto padre CounterManager, Dart automáticamente infiere ambos parámetros de tipo genérico:
// Recomendado - Dart infiere los tipos automáticamente
final count = watchValue((CounterManager m) => m.count);Firma del método:
R watchValue<T extends Object, R>(
ValueListenable<R> Function(T) selectProperty, {
bool allowObservableChange = false,
String? instanceName,
GetIt? getIt,
})Dart infiere:
T = CounterManager(del tipo del objeto padre)R = int(dem.countque esValueListenable<int>)
Sin la anotación de tipo, necesitarías especificar ambos genéricos manualmente:
// ❌️ Más verboso - parámetros de tipo manuales requeridos
final count = watchValue<CounterManager, int>((m) => m.count);Conclusión: ¡Siempre especifica el tipo del objeto padre en tu función selectora para código más limpio y legible!
Observando Múltiples Objetos
¿Necesitas observar datos de diferentes managers? Solo añade más llamadas watch:
class DashboardWidget extends WatchingWidget {
const DashboardWidget({super.key});
@override
Widget build(BuildContext context) {
// Watch different objects
final count = watchValue((CounterManager m) => m.count);
final userName = watchValue((SimpleUserManager m) => m.name);
final isLoading = watchValue((DataManager m) => m.isLoading);
return Column(
children: [
Text('Welcome, $userName!'),
Text('Counter: $count'),
if (isLoading) CircularProgressIndicator(),
],
);
}
}Cuando CUALQUIERA de ellos cambia, el widget se reconstruye. ¡Eso es todo!
Compara con ValueListenableBuilder:
class DashboardWidgetWithBuilders extends StatelessWidget {
const DashboardWidgetWithBuilders({super.key});
@override
Widget build(BuildContext context) {
final counter = di<CounterManager>();
final userManager = di<SimpleUserManager>();
final dataManager = di<DataManager>();
return ValueListenableBuilder<int>(
valueListenable: counter.count,
builder: (context, count, _) {
return ValueListenableBuilder<String>(
valueListenable: userManager.name,
builder: (context, userName, _) {
return ValueListenableBuilder<bool>(
valueListenable: dataManager.isLoading,
builder: (context, isLoading, _) {
return Column(
children: [
Text('Welcome, $userName!'),
Text('Counter: $count'),
if (isLoading) CircularProgressIndicator(),
],
);
},
);
},
);
},
);
}
}¡Tres niveles de anidación! Con watch_it, son solo tres líneas simples.
Ejemplo Real: Lista de Tareas
class TodoManager {
final todos = ValueNotifier<List<String>>([]);
void addTodo(String todo) {
todos.value = [...todos.value, todo]; // New list triggers update
}
}
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]),
);
}
}¿Añadir una tarea? El widget se reconstruye automáticamente. Sin setState, sin StreamBuilder.
Patrón Común: Estados de Carga
class DataWidget extends WatchingWidget {
@override
Widget build(BuildContext context) {
final isLoading = watchValue((DataManager m) => m.isLoading);
final data = watchValue((DataManager m) => m.data);
// Initialize data on first build
callOnce((_) {
di<DataManager>().fetchData();
});
if (isLoading) {
return CircularProgressIndicator();
}
return Text('Data: $data');
}
}Pruébalo Tú Mismo
Crea un
ValueNotifieren tu manager:dartclass MyManager { final message = ValueNotifier<String>('Hello'); }Regístralo:
dartvoid setupMyManager() { di.registerSingleton<MyManager>(MyManager()); }Obsérvalo:
dartclass MyWidget extends WatchingWidget { @override Widget build(BuildContext context) { final message = watchValue((MyManager m) => m.message); return Text(message); } }Cámbialo y observa la magia:
dartvoid changeMessage() { di<MyManager>().message.value = 'World!'; // Widget rebuilds! }
Puntos Clave
✅ watchValue() es tu función principal ✅ Una línea reemplaza listeners manuales y setState ✅ Funciona con cualquier ValueListenable<T> ✅ Suscripción y limpieza automáticas ✅ Múltiples llamadas watch = múltiples suscripciones
Siguiente: Aprende sobre más funciones watch para diferentes casos de uso.
Ver También
- WatchingWidgets - Qué tipo de widget usar (WatchingWidget, mixins, StatefulWidget)
- More Watch Functions - watchIt, watchPropertyValue, y más
- Watching Multiple Values - Patrones avanzados para combinar valores
- Watch Functions Reference - Referencia completa de la API