Skip to content

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:

  1. Suscribirse a cambios cuando el widget se construye
  2. Desuscribirse cuando el widget se dispone (prevenir memory leaks)
  3. 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:

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

dart
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 setState con 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. Incluso TextEditingController y AnimationController pueden crearse con createOnce() en WatchingWidget - ¡no se necesita StatefulWidget! Solo usa StatefulWidget para estado de UI verdaderamente local que requiere setState.

Alternativa: Usar Mixins

Si tienes widgets existentes que no quieres cambiar, usa mixins en su lugar:

Para StatelessWidget Existente

dart
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

dart
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 const con WatchItMixin
  • 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

dart
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

Publicado bajo la Licencia MIT.