Provides managing State which depends on other State in the Widget tree as well as reusable state (similar to hooks) for Flutter.
A Sub is a compact version of a StatefulWidget that creates, updates and disposes a Value.
Because almost all State in Flutter is bound to the tree, it is reasonable to come accross the requirement of creating State which depends on other State.
Here is how you create a stream from your database that is automatically recreated when the search changes in flutter_sub:
class Example extends StatelessWidget {
const Example({
super.key,
required this.search,
required this.database,
});
final String search;
final Database dabatase;
@override
Widget build(BuildContext context) {
return SubStream<String>(
create: () => database.search(widget.search),
keys: [database, search],
builder: (context, result) => /* ... */,
);
}
}
Pretty simple, right? If the search or database parameters change, our stream is recreated.
The SubStream
Widget takes care of creation the stream
and automatically recreates it whenever one of our dependencies, listed in keys
changes.
Here is the same code without flutter_sub:
class Example extends StatefulWidget {
const Example({
super.key,
required this.search,
required this.database,
});
final String search;
final Database dabatase;
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
late Stream<String> results;
@override
void initState() {
super.initState();
results = widget.database.search(widget.search);
}
@override
void didUpdateWidget(Example oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.search != oldWidget.search
|| widget.database != oldWidget.database) {
results = widget.database.search(widget.search);
}
}
@override
Widget build(BuildContext context) {
return StreamBuilder<String>(
stream: results,
builder: /* ... */,
);
}
}
In a normal StatefulWidget, we have to implement didUpdateWidget
and check every value manually.
This can lead to very repetetive and long code. The Sub Widgets simplify this process.
Sub Widgets are basically just a shortcut to creating a StatefulWidget
.
A State
is needed to hold the value and its keys.
To show the idea in pseudo-code:
var myValue;
@override
Widget build(BuildContext context) {
if (oldKeys != newKeys) {
// keys have changed, the value is recreated.
dispose(myValue);
myValue = create();
}
// the value is passed to the child widget.
return build(myValue);
}
This code is simplified. To see the full implementation, you can check out SubValueState.
You might have noticed that this library has certain similarities to flutter_hooks.
The primary purpose of this library is to provide convenient interstate relationships, but while building it, we noticed that it also allows the creation of easy resuable State
pieces.
As such, it can fill a similar role to hooks, while having some advantages:
-
Subs retain a syntax that is close to Flutter
Unlike hooks, Subs are Widgets and look and feel exactly like any part of the Widget tree. -
Subs do not replace Stateful or Stateless widgets, but compliment them
To use Subs you do not need to replace your Widgets with Hook variants. Subs work indepedently and in combination with the usual Flutter widgets. They can even be combined withStatefulWidgets
where theState
variables are used as Keys of the Sub. -
Subs are not magic
As seen in the above Principle sections, the implementation behind Subs is extremely simple. They are just a shortcut toStatefulWidget
. You dont have to learn something new or follow special rules. Its also very easy to make your own custom Subs.
The way Subs are built also comes with one "disadvantage":
Because Subs are Widgets, they will increase the nesting of your tree, unlike hooks which are completely flat.
We think Subs can be a neat alternative to hooks, so there is a Sub equivalent to many hooks from flutter_hooks
.
Replace your useSomething
with SubSomething
and you're good to go!
Some hooks may not have an equivalent Sub or the Sub may function slightly differently because it made more sense that way.
The package comes with many inbuilt and ready-to-use Subs.
Its also possible to directly use SubValue
in your code.
If you however wish to create your own reusable Subs, read ahead.
You can get access to SubValueBuild
and other internal types by importing package:flutter_sub/developer.dart
.
To create your own Subs easily, you can extend the SubValue
class.
For example, a simple implementation of SubTextEditingController
can look something like this:
class SubTextEditingController extends SubValue<TextEditingController> {
SubTextEditingController({
String? text,
super.keys,
required super.builder,
}) : super(
create: () => TextEditingController(text: text),
dispose: (controller) => controller.dispose(),
);
}
If you would like to include an extra Widget inside your custom Sub, you can do so by adding it to the builder.
Here is an example of a SubValueNotifier
that includes a ValueListenableBuilder
, so that its child is automatically rebuilt:
class SubValueNotifier<T> extends SubValue<ValueNotifier<T>> {
SubValueNotifier({
required T initialData,
required SubValueBuild<ValueNotifier<T>> builder,
super.keys,
}) : super(
create: () => ValueNotifier<T>(initialData),
builder: (context, notifier) => ValueListenableBuilder<T>(
valueListenable: notifier,
builder: (context, value) => builder(context, notifier),
),
dispose: (notifier) => notifier.dispose(),
);
}
If your custom Sub needs access to BuildContext
, for example to access an InheritedWidget
, you can use the SubValue.builder
constructor, which will provide BuildContext
to each function.
If you have even more special requirements like having a special Mixin on your State
, you can extend the SubValueState
of your SubValue
. An example of this can be seen in SubTickerProviderMixin.
This is provided for completeness sake. It might be easier to create a real StatefulWidget
instead.
The package comes with the following inbuilt Subs for your convenience:
Subs which help using Streams and Futures.
Name | Description |
---|---|
SubStream | Creates and subscribes to a Stream and returns its current state as an AsyncSnapshot . |
SubStreamController | Creates a StreamController which will automatically be disposed. |
SubSubscriber | Subscribes a listener to a Stream . |
SubFuture | Creates and subscribes to a Future and returns its current state as an AsyncSnapshot . |
Subs which help setting up Animations and ChangeNotifiers.
Name | Description |
---|---|
SubTickerProvider | Creates a TickerProvider . |
SubAnimationController | Creates an AnimationController which will be automatically disposed. |
SubAnimator | Subscribes to an Animation and returns its value. |
Subs which help using Listeners, ValueListeners and ValueNotifiers.
Name | Description |
---|---|
SubListener | Subscribes to a Listenable and rebuilds the widget whenever the listener is called. |
SubValueListener | Subscribes to a ValueListenable and return its value. |
SubValueNotifier | Creates a ValueNotifier which will be automatically disposed. |
Subs which create and hold various other controllers and objects commonly used.
Name | Description |
---|---|
SubTextEditingController | Creates and disposes a TextEditingController . |
SubFocusNode | Creates a FocusNode . |
SubTabController | Creates and disposes a TabController . |
SubScrollController | Creates and disposes a ScrollController . |
SubPageController | Creates and disposes a PageController . |
SubAppLifecycleState | Subscribes to the AppLifecycleState and return its value. |
SubTransformationController | Creates and disposes a TransformationController . |
SubPlatformBrightness | Subscribes to the platform's Brightness and return its value. |
Other miscellaneous subs.
Name | Description |
---|---|
SubValue | The base class for all Subs. |
SubDefault | Creates a default Value if none is provided. |
Subs that are similar to React Hooks to ease transition.
Name | Description |
---|---|
SubEffect | Useful for side-effects and optionally canceling them. |
SubState | Creates a variable and subscribes to it. |
SubMemoized | Caches the instance of a complex object. |