See context_plus for the ultimate convenience.
It combines context_watch with context_ref for effortless object propagation and observing.
Visit context-plus.sonerik.dev for more information and interactive examples.
context_ref
A convenient way of providing objects scoped to a BuildContext
and propagating them further down the tree.
Example
// Create a reference to an object
final _stream = Ref<Stream>();
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Bind an object to the scope of this BuildContext, so that it is:
// - initialized just once
// - automatically disposed together with the context
// - accessible by all children via the `Ref`
final stream = _stream.bind(context, () => Stream.periodic(const Duration(seconds: 1)));
// ... or bind it lazily, so that the object is initialized only upon first access via `Ref`
_stream.bindLazy(context, () => Stream.periodic(const Duration(seconds: 1)));
// ... or bind the pre-existing object to the `Ref` to expose it to the children
_stream.bindValue(context, existingStream);
// ... or just use the object within the scope of this BuildContext, without
// necessarily binding it to a `Ref`
final stream = context.use(
() => Stream.periodic(const Duration(seconds: 1)),
// Optionally, provide a `ref` parameter to bind the object to it
ref: _stream,
);
// ... use context.vsync whenever a TickerProvider is needed.
final animCtrl = context.use(
() => AnimationController(vsync: context.vsync, duration: duration),
);
return const _Child();
}
}
class _Child extends StatelessWidget {
const _Child();
@override
Widget build(BuildContext context) {
final stream = _stream.of(context);
// ... or use context_plus to observe the state of a bound object easily
final streamSnapshot = _stream.watch(context);
...
}
}
Installation
flutter pub add context_ref
Wrap your app in a ContextRef.root
widget.
ContextRef.root(
child: MaterialApp(...),
);
(Optional) Wrap default error handlers with ContextRef.errorWidgetBuilder()
and ContextRef.onError()
to get better hot reload related error messages.
void main() {
ErrorWidget.builder = ContextRef.errorWidgetBuilder(ErrorWidget.builder);
FlutterError.onError = ContextRef.onError(FlutterError.onError);
}
Usage
Initialize and use some object within the scope of this BuildContext
:
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
final stream = context.use(() => Stream.periodic(const Duration(seconds: 1)));
// Use StreamBuilder or context_watch to observe the current state of the stream
...
}
}
Each object use()
'd within a given BuildContext
is assigned the following key: (Type, Ref?, Key?)
. This key must be unique within the build
method.
So,
final stream1 = context.use(() async* { yield 1; });
final stream2 = context.use(() async* { yield 'hello'; });
final future1 = context.use(() async => 'Hey');
final future2 = context.use(() async => 0.0);
final notifier1 = context.use(() => ValueNotifier(0));
final notifier2 = context.use(() => ValueNotifier('Hey'));
will just work, while
final notifier1 = context.use(() => ValueNotifier(1));
final notifier2 = context.use(() => ValueNotifier(2));
would ask you to provide a unique key
, ref
(or their combination) to one of the use()
calls.
Use context_plus_lint to learn about the need to specify a key for the use()
call before even running the code.
Initialize and propagate a value down the widget tree:
final _state = Ref<_State>();
class Example extends StatelessWidget {
const Example({super.key});
@override
Widget build(BuildContext context) {
// No need to specify the `dispose` callback by hand if the _State is
// disposable via the `dispose` method.
//
// The `_State` will be created right await and disposed when the widget
// is removed from the widget tree.
//
// Use `bindLazy` if you want to create the value only when it's first accessed.
_state.bind(context, () => _State());
return const _Child();
}
}
class _Child extends StatelessWidget {
const _Child();
@override
Widget build(BuildContext context) {
final state = _state.of(context);
...
}
}
Provide an already-initialized value down the tree:
final _params = Ref<Params>();
class Example extends StatelessWidget {
const Example({
super.key,
required this.params,
});
final Params params;
@override
Widget build(BuildContext context) {
// Whenever the `params` value change, the `_Child` widget will get rebuilt.
// Values provided via `bindValue` are not disposed automatically.
_params.bindValue(context, params);
return const _Child();
}
}
class _Child extends StatelessWidget {
const _Child();
@override
Widget build(BuildContext context) {
final params = _params.of(context);
...
}
}