Pub Version Flutter CI codecov

A flutter package providing BuildContext extension methods to trigger a rebuild on change in Listenable (ChangeNotifier, ValueNotifier, etc).

What is Grab?

Grab is like a BuildContext extension version of ValueListenablebuiler or AnimatedBuilder. It comes with mixins and extension methods, grab() and grabAt(), which are similar to watch() and select() of package:provider.

If grab() or grabAt() is used in the build method of a widget that has the Grab mixin and given a Listenable such as ChangeNotifier or ValueNotifier, the widget is rebuilt whenever the Listenable is updated, and the extension method is triggered to grab and return the updated value.

class SignInButton extends StatelessWidget with Grab {
  const SignInButton();

  Widget build(BuildContext context) {
    final valid = context.grabAt(notifier, (SignInState s) => s.isInputValid);
    return ElevatedButton(
      onPressed: valid ? notifier.signIn : null,
      child: const Text('Sign in'),

Good for state management

What this package does is only rebuild a widget according to changes in a Listenable as stated above. Despite such simplicity, however, it becomes a powerful state management tool if combined with some DI package such as get_it and pot.

The Listenable does not have to be passed down the widget tree. Because Grab works as long as a Listenable is available in any way when grab() or grabAt() is used, you can use your favourite DI solution to pass around the Listenable.


The blog post below shows a picture of how simple state management could be. It inspired the author to create this package.

With Grab, instead of ValueListenableBuilder used in the article, combined with some sort of DI, you can focus on creating a good app with no difficulty understanding how to use it. The simplicity is an advantage over other packages with a larger API surface and too much functionality.

Supported Listenables

Anything that inherits the Listenable class:

  • ChangeNotifier
  • ValueNotifier
  • TextEditingController
  • Animation / AnimationController
  • ScrollController
  • etc.




Add StatelessGrabMixin or StatefulGrabMixin to a widget in which you want to use extensions.

class MyWidget extends StatelessWidget with StatelessGrabMixin
class MyWidget extends StatefulWidget with StatefulGrabMixin


Each mixin has an alias.

Use Grab for StatelessGrabMixin or Grabful for StatefulGrabMixin if you prefer a shorter name.

class MyWidget extends StatelessWidget with Grab
class MyWidget extends StatefulWidget with Grabful

Extension methods

grab() and grabAt() are available as extension methods of BuildContext. They are almost like watch() and select() of package:provider.

Make sure to add a mixin to the StatelessWidget / StatefulWidget where these methods are used. An GrabMixinError is thrown otherwise.


grab() listens to the Listenable passed to it, and the widget that the BuildContext belongs to is rebuilt every time the Listenable is updated.

final notifier = ValueNotifier(0);
Widget build(BuildContext context) {
  final count = context.grab<int>(counterNotifier);
  return Text('$count');

The return value is the Listenable itself, or its value if the Listenable is ValueListenable; that is, if the first parameter is:

  • Listenable other than ValueListenable (like ChangeNotifier and ScrollController)
    • The Listenable itself is returned.
  • ValueListenable (like ValueNotifier and TextEditingController)
    • The value of the Listenable is returned.

In the above example, the Listenable is a ValueNotifier extending ValueListenable, so the count returned by grab() is the value of ValueNotifier.

This is a little tricky, but has been designed that way for convenience.


grabAt() allows you to choose a value that a rebuild is triggered by and that is returned.

  • The widget is rebuilt only when there is a change in the value returned by the selector, which is a callback function passed as the second argument.
  • grabAt() returns the value of the target selected by the selector.
final notifier = ValueNotifier(
  Item(name: 'Milk', quantity: 3),
Widget build(BuildContext context) {
  final name = context.grabAt(notifier, (Item item) =>;
  return Text(name);

The selector receives the Listenable itself, or its value if the Listenable is ValueListenable.

In the above example, the listenable is a ValueNotifier extending ValueListenable, so its value, which is Item having name and quantity, is passed to the selector. The widget is rebuilt when name is updated, not when only quantity is updated, and the selected value (the value of name) is returned.


Value returned by selector

The value is not limited to a property value itself of the Listenable. It can be anything as long as it is possible to evaluate the equality with its previous value using the == operator.

final bool isEnough = context.grabAt(
  (Item item) => item.quantity > 5,

Supposing that the quantity was 3 in the previous build and has changed to 2 now, the widget is not rebuilt because there is no change in the value of isEnough; it has remained false.

Getting a property value without rebuilds

Grab is a package for rebuilding a widget, so it does not provide an equivalent of read() of the provider package. If you need a property value of a Listenable, you can just take it out of the Listenable without Grab.

This is one of the good things about this package. Because Grab does not care about how a Listenable is passed around, you can use your favourite DI solution to inject one and get it anywhere without the need of using BuildContext, and in a widget in the presentation layer where BuildContext is available, you can just use its extensions with the Listenable to control rebuilds of the widget.