context_plus 4.1.3 copy "context_plus: ^4.1.3" to clipboard
context_plus: ^4.1.3 copied to clipboard

Convenient value propagation and observing for Flutter. Utilize existing reactive value types more easily and with less boilerplate.

icon.png
context_plus #

showcase

context_plus context_plus context_plus context_plus

This package combines context_ref and context_watch into a single, more convenient package.

Visit context-plus.sonerik.dev for more information and interactive examples.

Table of Contents #

  1. Features
  2. Installation
  3. Supported observable types for .watch()
  4. 3rd party supported observable types for .watch() via separate packages
  5. API

Features #

  • Ref<T> - a reference to a value of type T bound to a context or multiple contexts.
  • Listenable/ValueListenable/AsyncListenable/Future/Stream (and more) or Ref of any of these types:
    • .watch(context) - rebuild the context whenever the observable notifies of a change. Returns the current value or AsyncSnapshot for corresponding types.

    • .watchOnly(context, (...) => ...) - rebuild the context whenever the observable notifies of a change, but only if selected value has changed.

    • .watchEffect(context, (...) => ...) - execute the provided callback whenever the observable notifies of a change without rebuilding the context.

    • Multi-value observing of up to 4 observables:

      final (value, futureSnap, streamSnap) =
          (valueListenable, future, stream).watch(context);
      // in any order, Refs are also supported
      final (streamSnap, value, futureSnap) =
          (streamRef, valueListenableRef, futureRef).watch(context);
      

      All three methods are available for all combinations of observables and observable Refs.

      ** Note: IDE suggestions for watch*() methods on records work only with Dart 3.6 and newer (see dart-lang/sdk#56572).

Installation #

  1. Add context_plus to your pubspec.yaml:
    flutter pub add context_plus
    
  2. Wrap your app in ContextPlus.root:
    ContextPlus.root(
      child: MaterialApp(...),
    );
    
  3. (Optional, but recommended) Wrap default error handlers with ContextPlus.errorWidgetBuilder() and ContextPlus.onError() to get better hot reload related error messages:
    void main() {
      ErrorWidget.builder = ContextPlus.errorWidgetBuilder(ErrorWidget.builder);
      FlutterError.onError = ContextPlus.onError(FlutterError.onError);
    }
    
  4. (Optional) Remove context_ref and context_watch from your pubspec.yaml if you have them.

Supported observable types for Observable.watch() and Ref<Observable>.watch(): #

  • Listenable, ValueListenable:
    • ChangeNotifier
    • ValueNotifier
    • AnimationController
    • ScrollController
    • TabController
    • TextEditingController
    • FocusNode
    • PageController
    • RefreshController
    • ... and any other Listenable derivatives
  • Future, SynchronousFuture
  • Stream
  • ValueStream (from rxdart)
  • AsyncListenable (from async_listenable)

3rd party supported observable types for Observable.watch() via separate packages: #

API #

Ref #

Ref<T> is a reference to a value of type T provided by a parent BuildContext.

It behaves similarly to InheritedWidget with a single value property and provides a conventional .of(context) method to access the value in descendant widgets.

Ref<AnyObservableType> also provides .watch() and .watchOnly() methods to observe the value conveniently.

Ref can be bound only to a single value per BuildContext. Child contexts can override their parents' Ref bindings.

Common places to declare Ref instances are:

  • As a global file-private variable.
    final _value = Ref<ValueType>();
    
    • Useful for sharing values across multiple closely-related widgets (e.g. per-screen values).
  • As a global public variable
    final appTheme = Ref<AppTheme>();
    
    • Useful for sharing values across the entire app.
  • As a static field in a widget class
    class SomeWidget extends StatelessWidget {
      static final _value = Ref<ValueType>();
      ...
    }
    
    • Useful for adding a state to a stateless widget without converting it to a stateful widget. The same applies to all previous examples, but this one is more localized, which improves readability for such use-case.

Ref.bind() #

T Ref<T>.bind(
  BuildContext context,
  T Function() create, {
  void Function(T value)? dispose,
  Object? key,
})
  • Binds a Ref<T> to the value initializer (create) for all descendants of context and context itself.
  • Value initialization happens immediately. Use .bindLazy() if you need it lazy.
  • Value is .dispose()'d automatically when the widget is disposed. Provide a dispose callback to customize the disposal if needed.
  • Similarly to widgets, key parameter allows for updating the value initializer when needed.

Ref.bindLazy() #

void Ref<T>.bindLazy(
  BuildContext context,
  T Function() create, {
  void Function(T value)? dispose,
  Object? key,
})

Same as Ref.bind(), but the value is created only when it's first accessed via Ref.of(context) or Ref.watch()/Ref.watchOnly(), thus not returned immediately.

Ref.bindValue() #

T Ref<T>.bindValue(
  BuildContext context,
  T value,
)
  • Binds a Ref<T> to the value for all descendants of context and context itself.
  • Whenever the value changes, the dependent widgets will be automatically rebuilt.
  • Values provided this way are not disposed automatically.

Ref.of(context) #

T Ref<T>.of(BuildContext context)

Ref<Observable>.watch() and Observable.watch() #

void Listenable.watch(BuildContext context)
void Ref<Listenable>.watch(BuildContext context)
  • Rebuilds the widget whenever the Listenable notifies about changes.
T ValueListenable<T>.watch(BuildContext context)
T Ref<ValueListenable<T>>.watch(BuildContext context)
  • Rebuilds the widget whenever the ValueListenable notifies about changes.
  • Returns the current value of the ValueListenable.
AsyncSnapshot<T> Future<T>.watch(BuildContext context)
AsyncSnapshot<T> Ref<Future<T>>.watch(BuildContext context)

AsyncSnapshot<T> Stream<T>.watch(BuildContext context)
AsyncSnapshot<T> Ref<Stream<T>>.watch(BuildContext context)

AsyncSnapshot<T> AsyncListenable<T>.watch(BuildContext context)
AsyncSnapshot<T> Ref<AsyncListenable<T>>.watch(BuildContext context)
  • Rebuilds the widget whenever the value notifies about changes.
  • Returns and AsyncSnapshot describing the current state of the value.
  • .watch()'ing a SynchronousFuture or ValueStream (from rxdart) will return a AsyncSnapshot with properly initialized data/error field, if initial value/error exists.
  • AsyncListenable can be used for dynamic swapping of the listened-to async value without losing the current state. See the live search example for a practical use-case.

Many popular observable types from 3rd party packages have their own .watch() methods provided by separate packages. See the 3rd party supported observable types for more information.

Ref<Observable>.watchOnly() and Observable.watchOnly() #

R TListenable.watchOnly<R>(
  BuildContext context,
  R Function(TListenable listenable) selector,
)
  • Invokes selector whenever the TListenable notifies about changes.
  • Rebuilds the widget whenever the selector returns a different value.
  • Returns the selected value.
R Future<T>.watchOnly<R>(
  BuildContext context,
  R Function(AsyncSnapshot<T> value) selector,
)

R Stream<T>.watchOnly<R>(
  BuildContext context,
  R Function(AsyncSnapshot<T> value) selector,
)
  • Invokes selector whenever the async value notifies about changes.
  • Rebuilds the widget whenever the selector returns a different value.
  • Returns the selected value.

Ref<Observable>.watchEffect() and Observable.watchEffect() #

void TListenable.watchEffect(
  BuildContext context,
  void Function(TListenable listenable) effect, {
  Object? key,
  bool immediate = false,
  bool once = false,
})

void Future<T>.watchEffect(
  BuildContext context,
  void Function(AsyncSnapshot<T> snapshot) effect, {
  Object? key,
  bool immediate = false,
  bool once = false,
})

void Stream<T>.watchEffect(
  BuildContext context,
  void Function(AsyncSnapshot<T> snapshot) effect, {
  Object? key,
  bool immediate = false,
  bool once = false,
})
  • Invokes effect on each value change notification.
  • Does not rebuild the widget on changes.
  • key parameter allows for uniquely identifying the effect, which is needed for conditional effects, immeadiate and once effects.
  • immediate parameter allows for invoking the effect immediately after binding. Requires a unique key. Can be combined with once.
  • once parameter allows for invoking the effect only once. Requires a unique key. Can be combined with immediate.
  • Can be used conditionally, in which case the .unwatchEffect() usage is recommended as well.

Ref<Observable>.unwatchEffect() and Observable.unwatchEffect()

void Listenable.unwatchEffect(
  BuildContext context, {
  required Object key,
})

void Future.unwatchEffect(
  BuildContext context, {
  required Object key,
})

void Stream.unwatchEffect(
  BuildContext context,
  required Object key,
)
  • Unregisters the effect with the specified key.
  • Useful for conditional effects where removing the effect ASAP is needed:
    if (condition) {
      observable.watchEffect(context, key: 'effect', ...);
    } else {
      observable.unwatchEffect(context, key: 'effect');
    }
    
19
likes
150
pub points
91%
popularity

Publisher

verified publishersonerik.dev

Convenient value propagation and observing for Flutter. Utilize existing reactive value types more easily and with less boilerplate.

Repository (GitHub)
View/report issues

Topics

#reactive #ui #state-management #extension

Documentation

API reference

License

MIT (license)

Dependencies

async_listenable, context_ref, context_watch, context_watch_base, flutter

More

Packages that depend on context_plus