Last updated:
0 purchases
grab
A flutter package providing extension methods to trigger a rebuild on change
in a Listenable (ChangeNotifier, ValueNotifier, etc).
There is a plan to support Stream too. Stay tuned!
What is Grab? #
Grab is like a method version of ValueListenablebuiler, AnimatedBuilder or
ListenableBuilder.
If grab() or grabAt() is called on a Listenable, the widget associated
with the provided BuildContext is rebuilt whenever the Listenable (or a
selected value) is updated, and as a result, the method "grab"s and returns
the updated value.
class UserProfile extends StatelessWidget {
const UserProfile();
@override
Widget build(BuildContext context) {
final userName = userNotifier.grabAt(context, (state) => state.name);
return Text(userName);
}
}
copied to clipboard
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 or 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.
Motivation #
The blog post below by someone else gave me the inspiration for this package.
It shows a picture of how simple state management could be.
Flutter state management for minimalists
Grab instead of ValueListenableBuilder used in the article, combined with
some sort of DI, lets you 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.
It is recommended to use Grab with subtypes of ValueListenable for type safety.
Please see the related section later in this document.
Examples #
Counters - simple
Useless Facts - simple
Todo app - basic
pub.dev explorer - advanced
Usage #
Getting started #
The Grab widget is necessary somewhere accessible via the tree from any widget
where grab extension methods are used. It is recommended the root widget is
wrapped as follows.
import 'package:grab/grab.dart';
...
void main() {
runApp(
const Grab(child: MyApp()),
);
}
copied to clipboard
Extension methods #
grab() and grabAt() are available as extension methods of Listenable and
ValueListenable. They are similar to watch() and select() of package:provider.
Make sure to have the Grab widget up in the tree. A GrabMissingError is
thrown otherwise.
grab()
grab() listens for changes in the Listenable that the method is called on.
Every time there is a change, it rebuilds the widget associated with the provided
BuildContext.
final notifier = ValueNotifier(0);
copied to clipboard
@override
Widget build(BuildContext context) {
final count = notifier.grab(context);
return Text('$count');
}
copied to clipboard
What is returned from the method depends on the type of the Listenable which the
method is called on:
ValueListenable (e.g. ValueNotifier, TextEditingController)
The value of the ValueListenable is returned.
Listenable other than ValueListenable (e.g. ChangeNotifier, ScrollController)
The Listenable itself is returned.
In the above example, the Listenable is a ValueNotifier, which is a subtype of
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()
grabAt() allows you to choose a value to be returned. The value is also used
to evaluate the necessity of a rebuild.
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 selected by the selector.
final notifier = ValueNotifier(
Item(name: 'Milk', quantity: 3),
);
copied to clipboard
@override
Widget build(BuildContext context) {
final name = notifier.grabAt(context, (item) => item.name);
return Text(name);
}
copied to clipboard
If the Listenable is ValueListenable or its subtype, the selector receives
its value. Otherwise, it receives the Listenable itself.
In the above example, the Listenable is a ValueNotifier, which is a subtype
of ValueListenable, so its value (an Item having name and quantity) is
passed to the selector. The widget is rebuilt when name is updated but not
when only quantity is updated, and the selected value (the value of name)
is returned.
Type safety #
The extension methods are more type-safe when used with a subtype of
ValueListenable (e.g. ValueNotifier).
ValueNotifier:
final valueNotifier = ValueNotifier(MyState);
// The type is inferred.
final state = valueNotifier.grab(context);
final prop = valueNotifier.grabAt(context, (state) => state.prop);
copied to clipboard
ChangeNotifier (not type-safe):
final changeNotifier = MyChangeNotifier();
// The type is not inferred, so needs to be annotated.
final notifier = changeNotifier.grab<MyChangeNotifier>(context);
final prop = changeNotifier.grabAt(context, (MyChangeNotifier notifier) => notifier.prop);
// Specifying a wrong type raises an error only at runtime.
changeNotifier.grab<AnotherChangeNotifier>(context);
copied to clipboard
Tips #
Value returned by selector #
The value is not limited to a field value 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 hasEnough = notifier.grabAt(context, (item) => item.quantity > 5);
copied to clipboard
Supposing that the quantity was 3 in the previous build and has changed to 2 now,
the widget is not rebuilt because the value returned by the selector has remained
false.
Getting a value without a rebuild #
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 field value of a Listenable,
you can just take it out of the Listenable without Grab.
DI (Dependency Injection) #
Grab does not care about how a Listenable is passed around, so you can use your
favourite DI solution to inject ones and get them anywhere in any layer.
Pottery by the same author is a good option for this purpose. It is a package
that helps you use Pot (a single-type DI container) in Flutter. Grab used
together with it provides a similar experience to using package:provider but
with more flexibility in that pots are available anywhere while their lifetime
is managed according to the lifecycle of widgets.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.