Skip to content

Watcher (State Management) ​

Overview ​

Watcher is a simple state management that utilizes Flutter's native ValueListenableBuilder and ValueNotifier, providing a lightweight, efficient, and straightforward way to manage state changes.

Initialization ​

dart
// you can use the `.watch` Extension.
final boolWatcher = false.watch; // Initializes a BoolWatcher
final intWatcher = 42.watch; // Initializes a IntWatcher
final listWatcher = [1, 2, 3].watch; // Initializes a ListWatcher<int>
// and so on for other types like List, Set, Map, num, Uri

// Or use the original Watchers.
final boolWatcher = BoolWatcher(true);
final listWatcher = ListWatcher<int>([1, 2, 3]);
final intWatcher = IntWatcher(10);
// and similarly for DoubleWatcher, StringWatcher, ColorWatcher, etc.

// You can also use the oringinal syntax.
final boolWatcher = Watcher<bool>(true);

// Flutter's ValueNotifier also supported
final boolWatcher = ValueNotifier<bool>(true);
// you can use the `.watch` Extension.
final boolWatcher = false.watch; // Initializes a BoolWatcher
final intWatcher = 42.watch; // Initializes a IntWatcher
final listWatcher = [1, 2, 3].watch; // Initializes a ListWatcher<int>
// and so on for other types like List, Set, Map, num, Uri

// Or use the original Watchers.
final boolWatcher = BoolWatcher(true);
final listWatcher = ListWatcher<int>([1, 2, 3]);
final intWatcher = IntWatcher(10);
// and similarly for DoubleWatcher, StringWatcher, ColorWatcher, etc.

// You can also use the oringinal syntax.
final boolWatcher = Watcher<bool>(true);

// Flutter's ValueNotifier also supported
final boolWatcher = ValueNotifier<bool>(true);

Using ValueWatcher Widget ​

dart

final isLoading = false.watch;


@override
Widget build(BuildContext context) {
  ...
	ValueWatcher<MyType>(
		builder: (context, value) => MyWidget(value), // Builder function
		watcher: isLoading, // Your watcher instance.
	),
  ...
}

// or with the `.watcher` Extension
@override
Widget build(BuildContext context) {
  ...
  isLoading.watcher(
    builder: (context, value) => MyWidget(value),
  ),
  ...
}
// my `MyWidget` here will automatically update its value, when isLoading.value changes.

final isLoading = false.watch;


@override
Widget build(BuildContext context) {
  ...
	ValueWatcher<MyType>(
		builder: (context, value) => MyWidget(value), // Builder function
		watcher: isLoading, // Your watcher instance.
	),
  ...
}

// or with the `.watcher` Extension
@override
Widget build(BuildContext context) {
  ...
  isLoading.watcher(
    builder: (context, value) => MyWidget(value),
  ),
  ...
}
// my `MyWidget` here will automatically update its value, when isLoading.value changes.

Handling Changes and Utilities ​

Description ​

Extensions such as updateIf, onChange, debounce, map, and combineWith provide powerful utilities for responding to changes, debouncing actions, transforming, or combining multiple Watcher instances.

Usage Examples ​

  • updateIf: Update the value conditionally.

    dart
    intWatcher.updateIf((val) => val < 10, 5);
    intWatcher.updateIf((val) => val < 10, 5);
  • onChange: Perform action on value change.

    dart
    stringWatcher.onChange((val) => print(val));
    // it returns call back that disposes the listener do not forget to call it in your close/dispose methods.
    stringWatcher.onChange((val) => print(val));
    // it returns call back that disposes the listener do not forget to call it in your close/dispose methods.
  • stream: Convert Watcher changes into a stream.

    dart
    final streamWatcher = valueWatcher.stream;
    streamWatcher.listen((value) {
      // Handle the stream of changes
    });
    final streamWatcher = valueWatcher.stream;
    streamWatcher.listen((value) {
      // Handle the stream of changes
    });
  • debounce: Debounce value changes.

    dart
    numWatcher.debounce(Duration(seconds: 1), (val) => print(val));
    numWatcher.debounce(Duration(seconds: 1), (val) => print(val));
  • map: Transform the value to another type.

    dart
    final stringWatcher = intWatcher.map((val) => val.toString());
    final stringWatcher = intWatcher.map((val) => val.toString());
  • combineWith: Combine with another Watcher.

    dart
    final combinedWatcher = intWatcher.combineWith(stringWatcher, (int a, String b) => '$a and $b');
    final combinedWatcher = intWatcher.combineWith(stringWatcher, (int a, String b) => '$a and $b');

Other Useful Helpers ​

Allowing direct manipulation of values in a more natural and concise way, mirroring the operations you'd typically perform on the data types themselves. Here are some samples:

Bool ​

  • toggle: Toggle the value of Watcher<bool>.

    dart
    boolWatcher.toggle(); // Toggles the boolean value
    boolWatcher.toggle(); // Toggles the boolean value

List ​

  • addAll: Add multiple items to Watcher<List<E>>.

  • remove: Remove an item from the list.

  • clear: Clear all items in the list.

    dart
    listWatcher.addAll([4, 5]); // Adds items to the list
    listWatcher.remove(item);   // Removes an item
    listWatcher.clear();        // Clears the list
    listWatcher.addAll([4, 5]); // Adds items to the list
    listWatcher.remove(item);   // Removes an item
    listWatcher.clear();        // Clears the list

Num/int/double ​

  • increment: Increment the value of Watcher<num>.

  • decrement: Decrement the value.

    dart
    numWatcher.increment(); // Increments the number
    numWatcher.decrement(); // Decrements the number
    numWatcher.increment(); // Increments the number
    numWatcher.decrement(); // Decrements the number

Map ​

  • putIfAbsent: Add a key-value pair if the key is not already in the map.

  • remove: Remove a key-value pair.

    dart
    mapWatcher.putIfAbsent(key, () => value); // Adds key-value pair if absent
    mapWatcher.remove(key);                  // Removes the key-value pair
    mapWatcher.putIfAbsent(key, () => value); // Adds key-value pair if absent
    mapWatcher.remove(key);                  // Removes the key-value pair

Set ​

  • add: Add an item to Watcher<Set<T>>.

  • removeAll: Remove all items from the set.

    dart
    setWatcher.add(item);       // Adds an item
    setWatcher.removeAll(items); // Removes all specified items
    setWatcher.add(item);       // Adds an item
    setWatcher.removeAll(items); // Removes all specified items

Uri ​

  • updatePath: Update the path of Watcher<Uri>.

    dart
    uriWatcher.updatePath(newPath); // Updates the URI path
    uriWatcher.updatePath(newPath); // Updates the URI path

Flutter Controllers With Watcher ​

The Watcher provides seamless integration with various native Flutter controllers, enhancing the development of reactive UIs. Below are examples demonstrating how Watcher can be utilized with common Flutter controllers:

TextEditingController ​

Watcher can reactively handle text input changes, making it ideal for scenarios like live character counts or conditional UI updates based on user input.

dart
final textEditingController = TextEditingController();

ValueWatcher(
  watcher: textEditingController,
  builder: (context, TextEditingValue value) {
    return Column(
      children: [
        TextField(controller: textEditingController),
        Text('Character Count: ${value.text.length}'),
      ],
    );
  },
);
final textEditingController = TextEditingController();

ValueWatcher(
  watcher: textEditingController,
  builder: (context, TextEditingValue value) {
    return Column(
      children: [
        TextField(controller: textEditingController),
        Text('Character Count: ${value.text.length}'),
      ],
    );
  },
);

AnimationController ​

Incorporate Watcher with AnimationController for dynamic UI elements that respond to animation state changes, enhancing the visual experience.

dart
final animationController = AnimationController(
  vsync: this,
  duration: Duration(seconds: 2),
)..repeat(reverse: true);

ValueWatcher(
  watcher: animationController,
  builder: (context, double value) {
    double scale = 1 + value; // Adjust scale based on animation value
    return Transform.scale(
      scale: scale,
      child: MyAnimatedWidget(),
    );
  },
);
final animationController = AnimationController(
  vsync: this,
  duration: Duration(seconds: 2),
)..repeat(reverse: true);

ValueWatcher(
  watcher: animationController,
  builder: (context, double value) {
    double scale = 1 + value; // Adjust scale based on animation value
    return Transform.scale(
      scale: scale,
      child: MyAnimatedWidget(),
    );
  },
);

ScrollController ​

Utilize Watcher with ScrollController to create responsive UI components that react to user's scroll behavior, such as showing or hiding elements based on scroll position.

dart
final scrollController = ScrollController();

Stack(
  children: [
    MyScrollableContent(controller: scrollController),
    ValueWatcher(
      watcher: scrollController,
      builder: (context, ScrollPosition value) {
        bool showTopButton =
            value.pixels > 500; // Show button after scrolling down
        return showTopButton
            ? Positioned(
                bottom: 20,
                right: 20,
                child: FloatingActionButton(
                  onPressed: () => scrollController.animateTo(0,
                      duration: Duration(seconds: 1),
                      curve: Curves.easeOut),
                  child: Icon(Icons.arrow_upward),
                ),
              )
            : SizedBox.shrink();
      },
    ),
  ],
);
final scrollController = ScrollController();

Stack(
  children: [
    MyScrollableContent(controller: scrollController),
    ValueWatcher(
      watcher: scrollController,
      builder: (context, ScrollPosition value) {
        bool showTopButton =
            value.pixels > 500; // Show button after scrolling down
        return showTopButton
            ? Positioned(
                bottom: 20,
                right: 20,
                child: FloatingActionButton(
                  onPressed: () => scrollController.animateTo(0,
                      duration: Duration(seconds: 1),
                      curve: Curves.easeOut),
                  child: Icon(Icons.arrow_upward),
                ),
              )
            : SizedBox.shrink();
      },
    ),
  ],
);

Full Counter Example ​

dart
import 'package:flutter/material.dart';
import 'package:flutter_helper_utils/flutter_helper_utils.dart';

main() => runApp(MyCounter());

class MyCounter extends StatelessWidget {
  MyCounter({super.key});

  // this can also be initialized in another class (e.g. singleton class)
  // and change its value any where for more scaled state management
  final IntWatcher counter = 0.watch;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Watch Counter',
      home: Scaffold(
        appBar: AppBar(title: const Text('Watch Counter')),
        body: Center(
          child: ValueWatcher(
            watcher: counter,
            builder: (context, value) {
              return Text('Counter: $value');
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => counter.increment(1),
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:flutter_helper_utils/flutter_helper_utils.dart';

main() => runApp(MyCounter());

class MyCounter extends StatelessWidget {
  MyCounter({super.key});

  // this can also be initialized in another class (e.g. singleton class)
  // and change its value any where for more scaled state management
  final IntWatcher counter = 0.watch;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Watch Counter',
      home: Scaffold(
        appBar: AppBar(title: const Text('Watch Counter')),
        body: Center(
          child: ValueWatcher(
            watcher: counter,
            builder: (context, value) {
              return Text('Counter: $value');
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => counter.increment(1),
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}