Skip to content

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

dart
// 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 a CounterManager desde get_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:

dart
//  Recomendado - Dart infiere los tipos automáticamente
final count = watchValue((CounterManager m) => m.count);

Firma del método:

dart
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 (de m.count que es ValueListenable<int>)

Sin la anotación de tipo, necesitarías especificar ambos genéricos manualmente:

dart
// ❌️ 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:

dart
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:

dart
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

dart
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

dart
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

  1. Crea un ValueNotifier en tu manager:

    dart
    class MyManager {
      final message = ValueNotifier<String>('Hello');
    }
  2. Regístralo:

    dart
    void setupMyManager() {
      di.registerSingleton<MyManager>(MyManager());
    }
  3. Obsérvalo:

    dart
    class MyWidget extends WatchingWidget {
      @override
      Widget build(BuildContext context) {
        final message = watchValue((MyManager m) => m.message);
        return Text(message);
      }
    }
  4. Cámbialo y observa la magia:

    dart
    void 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

Publicado bajo la Licencia MIT.