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 Ref
s 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_plus
to 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_lint
to yourpubspec.yaml
Update theflutter pub add --dev context_plus_lint flutter pub add --dev custom_lint
analysis_options.yaml
to includeanalyzer: plugins: - custom_lint
- (Optional) Remove
context_ref
andcontext_watch
from yourpubspec.yaml
if you have them.
Features
context.use(() => ...)
- Bind an object to the scope of this
BuildContext
, so that it is:- initialized just once per
BuildContext
lifetime (unless thekey
orref
parameter 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 typeT
bound to acontext
or multiplecontext
s..bind(context, () => ...)
- create and bind value to acontext
. Automaticallydispose()
the value uponcontext
disposal..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 thecontext
or its nearest ancestor.
Listenable
/ValueListenable
/Future
/Stream
(and more) orRef
of any of these types:-
.watch(context)
- rebuild thecontext
whenever the observable notifies of a change. Returns the current value orAsyncSnapshot
for corresponding types. -
.watchOnly(context, (...) => ...)
- rebuild thecontext
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 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
: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:
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
T
by calling thecreate
callback immeditely ifT
wasn't created yet, otherwise just returns the existingT
value. - Keeps
T
alive for the lifespan of a givenBuildContext
. - Automatically disposes
T
whenBuildContext
is disposed by calling thedispose
callback (if provided) orT.dispose()
method (if callback is not provided). - Binds
T
to theref
, if provided. - Similarly to widgets,
key
parameter 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 ofcontext
andcontext
itself. - Value initialization happens immediately. Use
.bindLazy()
if you need it lazy. - Value is
.dispose()
'd automatically when the widget is disposed. Provide adispose
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 thevalue
for all descendants ofcontext
andcontext
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)
- Fetches the value of type
T
provided 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
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 aSynchronousFuture
orValueStream
(from rxdart) will return aAsyncSnapshot
with properly initializeddata
/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,
)
R ValueListenable<T>.watchOnly<R>(
BuildContext context,
R Function(T value) selector,
)
- Invokes
selector
whenever theListenable
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 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
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
andonce
effects.immediate
parameter allows for invoking the effect immediately after binding. Requires a uniquekey
. Can be combined withonce
.once
parameter 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'); }