watch<T> method

  1. @override
T watch<T>(
  1. ProviderListenable<T> listenable
)
override

Obtains the state of a provider and causes the state to be re-evaluated when that provider emits a new value.

Using watch allows supporting the scenario where we want to re-create our state when one of the object we are listening to changed.

This method should be your go-to way to make a provider read another provider – even if the value exposed by that other provider never changes.

Use-case example: Sorting a todo-list

Consider a todo-list application. We may want to implement a sort feature, to see the uncompleted todos first.
We will want to create a sorted list of todos based on the combination of the unsorted list and a sort method (ascendant, descendant, ...), both of which may change over time.

In this situation, what we do not want to do is to sort our list directly inside the build method of our UI, as sorting a list can be expensive. But maintaining a cache manually is difficult and error prone.

To solve this problem, we could create a separate Provider that will expose the sorted list, and use watch to automatically re-evaluate the list only when needed.

In code, this may look like:

final sortProvider = StateProvider((_) => Sort.byName);
final unsortedTodosProvider = StateProvider((_) => <Todo>[]);

final sortedTodosProvider = Provider((ref) {
  // listen to both the sort enum and the unfiltered list of todos
  final sort = ref.watch(sortProvider);
  final todos = ref.watch(unsortedTodosProvider);

  // Creates a new sorted list from the combination of the unfiltered
  // list and the filter type.
  return [...todos].sort((a, b) { ... });
});

In this code, by using Provider + watch:

  • if either sortProvider or unsortedTodosProvider changes, then sortedTodosProvider will automatically be recomputed.
  • if multiple widgets depends on sortedTodosProvider the list will be sorted only once.
  • if nothing is listening to sortedTodosProvider, then no sort is performed.

Implementation

@override
T watch<T>(ProviderListenable<T> listenable) {
  _assertNotOutdated();
  assert(!_debugIsRunningSelector, 'Cannot call ref.watch inside a selector');

  if (listenable is! ProviderBase<T>) {
    final sub = listen<T>(
      listenable,
      (prev, value) => _markDependencyChanged(),
      onError: (err, stack) => _markDependencyChanged(),
      onDependencyMayHaveChanged: _markDependencyMayHaveChanged,
    );

    return sub.read();
  }

  assert(_debugAssertCanDependOn(listenable), '');

  final element = _container.readProviderElement(listenable);
  _dependencies.putIfAbsent(element, () {
    final previousSub = _previousDependencies?.remove(element);
    if (previousSub != null) {
      return previousSub;
    }

    assert(
      () {
        // Flushing the provider before adding a new dependency
        // as otherwise this could cause false positives with certain asserts.
        // It's done only in debug mode since `readSelf` will flush the value
        // again anyway, and the only value of this flush is to not break asserts.
        element.flush();
        return true;
      }(),
      '',
    );

    element
      .._onListen()
      .._providerDependents.add(this);

    return Object();
  });

  return element.readSelf();
}