context_plus 6.0.5
context_plus: ^6.0.5 copied to clipboard
Convenient value propagation and observing for Flutter. Utilize existing reactive value types more easily and with less boilerplate.
Visit context-plus.sonerik.dev for more information and interactive examples.
context_plus #
This package combines context_ref and context_watch into a single, more convenient package.
It adds .watch(), .watchOnly(), .watchEffect() extension methods on the Refs to supported observable types.
Example #
// Create a reference to any object
final _stream = Ref<Stream>();
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Bind the stream to this context and observe its state
final streamSnapshot = context.use(
() => Stream.periodic(const Duration(seconds: 1)),
// Optionally, provide a `ref` parameter to bind the stream to it
ref: _stream,
).watch(context);
// ... or bind the stream lazily, so that it is initialized only upon first access via the `Ref`
_stream.bindLazy(context, () => Stream.periodic(const Duration(seconds: 1)));
// ... or bind the pre-existing stream to the `Ref` to expose it to the children
_stream.bindValue(context, existingStream);
return const _Child();
}
}
class _Child extends StatelessWidget {
const _Child();
@override
Widget build(BuildContext context) {
// Observe the current state of a provided stream easily
final streamSnapshot = _stream.watch(context);
// The same works for most observable types:
// - Stream, ValueStream, etc.
// - Future, SynchronousFuture, FutureOr, etc.
// - Listenable, ValueListenable and their descendants
// - 3rd party observable types, such as:
// - `Bloc`, `Cubit` from flutter_bloc
// - `Signal` from signals
// - `Observable` from mobx
// - `Rx` from getx
...
}
}
Table of Contents #
- Installation
- Features
- Supported observable types for
.watch() - 3rd party supported observable types for
.watch()via separate packages - API
Installation #
- Add
context_plusto yourpubspec.yaml:flutter pub add context_plus - Wrap your app in
ContextPlus.root:ContextPlus.root( child: MaterialApp(...), ); - (Optional, but recommended) Wrap default error handlers with
ContextPlus.errorWidgetBuilder()andContextPlus.onError()to get better hot reload related error messages:void main() { ErrorWidget.builder = ContextPlus.errorWidgetBuilder(ErrorWidget.builder); FlutterError.onError = ContextPlus.onError(FlutterError.onError); } - (Optional, but recommended) Add
context_plus_lintto yourpubspec.yaml
Update theflutter pub add --dev context_plus_lint flutter pub add --dev custom_lintanalysis_options.yamlto includeanalyzer: plugins: - custom_lint - (Optional) Remove
context_refandcontext_watchfrom yourpubspec.yamlif you have them.
Features #
context.use(() => ...)- Bind an object to the scope of this
BuildContext, so that it is:- initialized just once per
BuildContextlifetime (unless thekeyorrefparameter value changes) - automatically disposed together with
BuildContext - accessible by all children via the provided
Ref(ifref:parameter is specified)
- initialized just once per
- Bind an object to the scope of this
Ref<T>- a reference to a value of typeTbound to acontextor multiplecontexts..bind(context, () => ...)- create and bind value to acontext. Automaticallydispose()the value uponcontextdisposal..bindLazy(context, () => ...)- same as.bind(), but the value is created only when it's first accessed..bindValue(context, ...)- bind an already created value to acontext. The value is not disposed automatically..of(context)- get the value bound to thecontextor its nearest ancestor.
Listenable/ValueListenable/Future/Stream(and more) orRefof any of these types:-
.watch(context)- rebuild thecontextwhenever the observable notifies of a change. Returns the current value orAsyncSnapshotfor corresponding types. -
.watchOnly(context, (...) => ...)- rebuild thecontextwhenever 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 thecontext. -
Multi-value observing of up to 4 values:
// Observe multiple values from observable objects final (value, futureSnap, streamSnap) = (valueListenable, future, stream).watch(context); // or .watchOnly(context, (...) => ...); // or .watchEffect(context, (...) => ...); // Observe multiple values from Refs to observable objects final (streamSnap, value, futureSnap) = (streamRef, valueListenableRef, futureRef).watch(context); // or .watchOnly(context, (...) => ...); // or .watchEffect(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).
-
Supported observable types for Observable.watch() and Ref<Observable>.watch(): #
Listenable,ValueListenable:ChangeNotifierValueNotifierAnimationControllerScrollControllerTabControllerTextEditingControllerFocusNodePageControllerRefreshController- ... and any other
Listenablederivatives
Future,SynchronousFutureStreamValueStream(from rxdart)AsyncListenable(from async_listenable)
3rd party supported observable types for Observable.watch() via separate packages: #
Bloc,Cubit(from bloc, using context_watch_bloc)Observable(from mobx, using context_watch_mobx)Rx(from get, using context_watch_getx)Signal(from signals, using context_watch_signals)
API #
context.use(() => ...) #
T BuildContext.use<T>(
T Function() create, {
void Function(T value)? dispose,
Ref<T>? ref,
Object? key,
});
- Creates a value
Tby calling thecreatecallback immeditely ifTwasn't created yet, otherwise just returns the existingTvalue. - Keeps
Talive for the lifespan of a givenBuildContext. - Automatically disposes
TwhenBuildContextis disposed by calling thedisposecallback (if provided) orT.dispose()method (if callback is not provided). - Binds
Tto theref, if provided. - Similarly to widgets,
keyparameter allows for updating the value initializer.
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 ofcontextandcontextitself. - Value initialization happens immediately. Use
.bindLazy()if you need it lazy. - Value is
.dispose()'d automatically when the widget is disposed. Provide adisposecallback to customize the disposal if needed. - Similarly to widgets,
keyparameter 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 thevaluefor all descendants ofcontextandcontextitself. - 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)
- Fetches the value of type
Tprovided by theRef<T>.bind()/Ref<T>.bindLazy()/Ref<T>.bindValue()in the nearest ancestor ofcontext. - Rebuilds the widget whenever the value provided with
Ref<T>.bindValue()changes. - Throws an exception if the value is not provided in the ancestor tree.
Ref<Observable>.watch() and Observable.watch() #
TListenable TListenable.watch(BuildContext context)
TListenable Ref<TListenable>.watch(BuildContext context)
- Rebuilds the widget whenever the
Listenablenotifies about changes.
T ValueListenable<T>.watch(BuildContext context)
T Ref<ValueListenable<T>>.watch(BuildContext context)
- Rebuilds the widget whenever the
ValueListenablenotifies 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
AsyncSnapshotdescribing the current state of the value. .watch()'ing aSynchronousFutureorValueStream(from rxdart) will return aAsyncSnapshotwith properly initializeddata/errorfield, if initial value/error exists.AsyncListenablecan 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,
)
R ValueListenable<T>.watchOnly<R>(
BuildContext context,
R Function(T value) selector,
)
- Invokes
selectorwhenever theListenablenotifies about changes. - Rebuilds the widget whenever the
selectorreturns 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
selectorwhenever the async value notifies about changes. - Rebuilds the widget whenever the
selectorreturns 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 ValueListenable<T>.watchEffect(
BuildContext context,
void Function(T value) 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
effecton each value change notification. - Does not rebuild the widget on changes.
keyparameter allows for uniquely identifying the effect, which is needed for conditional effects,immeadiateandonceeffects.immediateparameter allows for invoking the effect immediately after binding. Requires a uniquekey. Can be combined withonce.onceparameter allows for invoking the effect only once. Requires a uniquekey. Can be combined withimmediate.- 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'); }
