0 purchases
valuable
Valuable #
About #
What is Valuable ?
Valuable is another state management library (one more...), it takes its roots from Riverpod and from which it is inspired.
Why Valuable ?
Unlike Riverpod, Valuable each of its Provider-like is autonomous and remain on itself, it dispatch its own update events.
In fact, Valuable is a graph state management.
It was made to build Widget tree as stateless as possible, with the ability to refresh some part of the tree, without the necessity to split it to an infinite number of StatelessWidget.
While Riverpod needs to have its Providers global, Valuable tends to have its owns local, or in some kind of a ViewModel.
In my mind, when I built this library, I went with the idea, that Riverpod and Valuable will not be concurrent, but complementary :
Riverpod for the global state of the app
Valuable for each local state (Widget, Views, ...)
How it works ?
At this time, Valuable depends on Flutter, because it uses its ChangeNotifier class. It is something I think about, and may change in the future to create a non flutter dependent package.
Like it was said, Valuable is a graph state management. It was designed to works inside AND outside the Widget Tree.
Each node, obviously named a Valuable 🎉, can depend on some other nodes and more. If a node in this graph becomes invalid, then it notifies all its listeners, which become invalid too; It works like a flow, to invalidate all graph segments that need to be reevaluate.
How to use Valuable ? #
Declare a Valuable which matches the behaviour you want.
StatefulValuable<T> for Valuable that can be setted, the most used
FutureValuable<Output, Res> that manages to provide an Output from a Future<Res> in each of its states.
StreamValuable<Output, Msg> that manages to provide an Output from a Stream<Msg>
Valuable<T> otherwise. Can be an immutable value, or an evaluative function. This is the root type of all Valuable
For all Valuable #
Read current value
Valuable<T> myValuable = ...
myValuable.getValue(); // Get the current value of the valuable (read state or evaluate it)
copied to clipboard
In some case, getValue requires a ValuableContext that can contain special informations (like a BuildContext for example).
ValuableContext is not mandatory, and can provide extensibility for the future.
Listen for change
As a Valuable inherits from ChangeNotifier, its value's change can be listen by
myValuable.addListener(() {
// Value has change here or have been reevaluated !
}
copied to clipboard
Obviously, that's not how we'll use it in a Flutter's widget tree, but we'll see that later.
Invalidate it
Sometime, it could be useful to mark the Valuable as invalid for it to reevaluate its value.
myValuable.markToReevaluate();
copied to clipboard
Compare it
Valuable redefines few common operators to compare themselves. It's able to compare to its generic type directly too.
Available operators are :
>
<
<=
>=
Obviously, it's impossible to reuse == and != operators, so in these cases, 2 functions have been created:
equals
notEquals
Some examples
Valuable<int> a = ...
Valuable<int> b = ...
Valuable<bool> equality = a.equals(b);
copied to clipboard
Whenever a or b change, equality is notified, and notify itself all its listeners, that IS the point of Valuable
Valuable<int> a = ...
int b = ...
Valuable<bool> equality = a.equals(b);
copied to clipboard
It also works, but equality notifies only on a changes.
StatefulValuable<T> #
As it was said, StatefulValuable<T> are the most used Valuable as it's the only one we can directly affect.
It's instanciated with a value, and can be changed at anytime and anywhere we can access it.
At any change, all listeners are notified.
Instanciate it
StatefulValuable<int> counter = StatefulValuable<int>(0);
copied to clipboard
That's all !
Set it
counter.setValue(1);
copied to clipboard
FutureValuable<Output, Res> #
FutureValuable<Output, Res> have been created in the purpose of computing a Valuable<Future<Res>> to a safe runtime Output value. In fact FutureValuable<Output, Res> inherits Valuable<Output>, so it can use all of its useful methods.
There are 2 constructors that can be written.
FutureValuable computing constructor
late final Valuable<Future<int>> distantCounter = ...
late final FutureValuable<String, int> distantCounterStr = FutureValuable<String, int>(
distantCounter,
dataValue: (ValuableContext? context, int result) => "My counter is $result", // Future is done
noDataValue: (ValuableContext? context) => "Still in progress", // Future is not done yet
errorValue: (ValuableContext? context, Object error, StackTrace st) => "Can't retrieve counter !", // Future done in error
);
copied to clipboard
This way, we can provide a value, depending of the Valuable<Future<Res>> state and value.
FutureValuable providing constructor
This constructor is the simpliest for the case Res == Output, and provide value for waiting and error states.
late final Valuable<Future<int>> distantCounter = ...
late final FutureValuable<int, int> distantCounterVal = FutureValuable<int, int>.values(
distantCounter,
noDataValue: 0, // Future is not done yet
errorValue: -1, // Future done in error
);
copied to clipboard
Then the Valuable always have a correct runtime value, without error management complexity.
FutureValuable AsyncValue 'constructor'
In some cases, it's not the desired behavior to map to a certain type Output, but it's necessary to map dynamically during runtime.
For that, it's possible to use this code below.
late final Valuable<Future<int>> distantCounter = ...
late final FutureValuableAsyncValue<int> distantCounterVal = FutureValuable.asyncVal(distantCounter);
copied to clipboard
The Valuable returns a ValuableAsyncValue<int>. This kind of object is really useful as it offers a map() method to compute the current state of the Future.
StreamValuable<Output, Msg> #
StreamValuable<Output, Msg> works exactly the same as FutureValuable<Output, Msg>, but remains on a Stream instead a Future.
Let show the code directly !
StreamValuable computing constructor
late final Valuable<Stream<int>> continuousCounter = ...
late final StreamValuable<String, int> continuousCounterStr = StreamValuable<String, int>(
continuousCounter,
dataValue: (ValuableContext? context, int result) => "$result", // Stream data
doneValue: (ValuableContext? context) => "Done.", // Stream done
errorValue: (ValuableContext? context, Object error, StackTrace st) => "On error !", // Stream in error
initialValue: "0",
);
copied to clipboard
Valuable<T> #
As it was said, Valuable<T> is the root type of all Valuable, but it offer two factories for :
simple immutable value, to interact with others Valuable
auto evaluated Valuable, that can depend on others Valuable
Simple immutable value
final Valuable<int> zero = Valuable.value(0);
copied to clipboard
Link to a ValueListenable
late final AnimatedController controller = AnimatedController();
late final Valuable<double> vcont = Valuable<double>.listenable(controller);
copied to clipboard
Auto evaluated
final StatefulValuable<int> counter = StatefulValuable<int>(2);
final late Valuable<double> halfCounter = Valuable.evaluate((ValuableWatcher watch) => watch(counter) / 2);
...
print(halfCounter.getValue()); // Print '1'
counter.setValue(3); // halfCounter is notified of this change, marks as invalid, and notifies all its listeners
print(halCounter.getValue()); // Print '1.5'
copied to clipboard
Here comes the real power of Valuable.
This way, the Valuables can be chained and then the graph is created.
The differents states are defined directly by the Valuable valuer and are safely used in the code through it.
Some special Valuable #
There are some special derivatives of Valuable, that can not really define how to get a value.
These just link to another Valuable to let it provide the value, but instead they allow extra behaviors.
ValuableLinker
The ValuableLinker is just an other Valuable, that just needs to have a default value.
But its main purpose is to link to another Valuable of the same generic type, to watch and provide a value.
This way, we can delegate to a deeper node of the widget tree, the management of a Valuable (that follows an Animation for example), and pass through the tree, a ValuableLinker to link to it.
The linker became aware of any Valuable changes !
Two methods are available on ValuableLinker<T>, link(Valuable<T>) and unlink().
As it was said, the main purpose of this class is to link to an other Valuable, but ValuableLinker can't link if already linked. In this case, a StateError is thrown.
You shall unlink the ValuableLinker before reuse the link method (it was an arbitrary choice to make it explicit).
For a complete example, just refer to sample_linker.dart in the example.
HistorizedValuable
HistorizedValuable is the contract class to describe a Valuable that can maintain an history for all values of a Valuable.
Historize a Valuable is as simple as this:
final historized = Valuable<T>().historize();
copied to clipboard
The historize() is available on the Valuable base type.
HistorizedValuable is derivated to HistorizedStatefulValuable to historize the StatefulValuable and HistorizedValuableLinker to historize ValuableLinker. This way, it can provide the transitive methods to each types (setValue, link, etc.).
HistorizedValuable provide an UnmodifiableQueueView<ValuableHistoryNode> get history accessor, that list all history node for a Valuable.
StatefulValuable comes with an historizeRW method too. This method provide a ReWritableHistorizedValuable that is a StatefulValuable with extra methods/accessor to play with history:
canUndo, accessor to know if undo is possible
canRedo, accessor to know if redo is possible
undoToInitial(), that set the value to initial value in the history
undo(), that set the value to the previous in the history
redo(), that set the value to the next value in the history
redoToCurrent(), that set the value to latest value (current) in the history
Take a look to the great example in sample_history.dart
Valuable and the Widget tree #
Like any other Flutter state management, Valuable is designed to provide interaction with the Widget tree.
Here are the different concepts to use Valuable in Flutter UI:
ValuableConsumer
ValuableWidget
watchIt extension
ValuableConsumer #
This is the most common way to use some Valuable inside the Widget tree, in purpose to produce a reactive UI.
Inspired by Riverpod, this widget requires a ValuableConsumerBuilder that provide :
a BuildContext context
a T watch(Valuable<T>) function to read the value, and especially to register at any changes of the Valuable<T>
a Widget? child that can be passed as optional argument of the ValuableConsumer
Let the code speaks
final StatefulValuable<Color> myColor = StatefulValuable<Color>(Colors.red);
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
color: Colors.amber,
width: 100,
height: 100,
),
ValuableConsumer(
builder: (BuildContext context, ValuableWatcher watch, _) =>
Container(
color: watch(myColor),
width: 100,
height: 100,
),
),
Row(
children: <Widget>[
TextButton(
onPressed: () => myColor.setValue(Colors.blue),
child: const Text("Blue"),
),
TextButton(
onPressed: () => myColor.setValue(Colors.red),
child: const Text("Red"),
),
],
),
],
);
}
copied to clipboard
In this example, we build an UI with 2 colored squares and 2 buttons.
The first square is designed to never change, whereas the second can changed its color when we pressed either of the buttons.
When we set the value of myColor by pressing a button, only the builder of the ValuableConsumer is played again in order to change the color.
This way, we create a databinding between myColor and the UI.
ValuableWidget #
In some cases, you may want to define a reusable Widget that depends on one or more Valuable.
ValuableWidget exists for this reason. It's exactly the same as declare a new StatelessWidget where the built Widget is a ValuableConsumer, but without the boilerplate.
Instead, ValuableWidget.build gains access to the function watch as it exists in the ValuableConsumer.builder.
Let's redo the same code as above, but isolate the ValuableConsumer as Widget, to reuse it later.
class ColoredSquare extends ValuableWidget {
/// No need to know that is a StatefulValuable or other, only need to depend on a Valuable<Color>
final Valuable<Color> myColor;
const ColoredSquare({
required this.myColor,
Key? key,
}) : super(key: key);
Widget build(BuildContext context, ValuableWatcher watch) {
return Container(
color: watch(myColor),
width: 100,
height: 100,
);
}
}
copied to clipboard
and then use it
final StatefulValuable<Color> myColor = StatefulValuable<Color>(Colors.red);
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
color: Colors.amber,
width: 100,
height: 100,
),
ColoredSquare(
myColor: myColor,
),
Row(
children: <Widget>[
TextButton(
onPressed: () => myColor.setValue(Colors.blue),
child: const Text("Blue"),
),
TextButton(
onPressed: () => myColor.setValue(Colors.red),
child: const Text("Red"),
),
],
),
],
);
}
copied to clipboard
The behavior is the same, but we gain the possibility to reuse the ColoredSquare and to separe the concerns.
watchIt #
This is an extension on Valuable<T>, that allows to retrieve the closest ValuableConsumer in the tree and use its watch function to read the value and to subscribe to the Valuable changes.
If no ValuableConsumer are found in the tree, the value is simply returned to avoid runtime error.
Code sample
final StatefulValuable<Color> myColor = StatefulValuable<Color>(Colors.red);
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
color: Colors.amber,
width: 100,
height: 100,
),
Container(
color: myColor.watchIt(context),
width: 100,
height: 100,
),
),
Row(
children: <Widget>[
TextButton(
onPressed: () => myColor.setValue(Colors.blue),
child: const Text("Blue"),
),
TextButton(
onPressed: () => myColor.setValue(Colors.red),
child: const Text("Red"),
),
],
),
],
);
}
copied to clipboard
The usage of this method is not encouraged.
It can be useful in some cases, but it doesnt separe the rebuilt parts and tends to invalidate to much UI (performance shortage). Use at your own risks.
Go deeper #
Some explanations to go deeper with Valuable
Extensions #
The library defines few extensions to add some functionality to certain generic types of Valuable.
It can be operators, but methods too.
It often results in a Valuable that can be notified from the source Valuable, in order to reevaluate itself.
The simpliest extensions are listed there.
Valuable<bool>
& operator between 2 Valuable<bool> to create a Valuable<bool> that result of an and operation.
| operator, to create a Valuable<bool> that result of an or operation.
negation() method, generate a Valuable<bool> that is the negation of the caller.
Valuable<T extends num>
+ operator that produces a Valuable<num>, result of the sum.
- operator that produces a Valuable<num>, result of the substraction.
* operator that produces a Valuable<num>, result of the multiplication.
/ operator that produces a Valuable<double>, result of the division.
% operator that produces a Valuable<num>, result of the modulo.
~/ operator that produces a Valuable<int>, result of the integer division.
-Valuable<num> that produces a Valuable<num>, that is the negated value.
Valuable<String>
+ operator that produces a Valuable<String>, result of the concatenation between two Valuable<String>
StatefulValuable<bool>
negate() that changes the value to the opposite boolean value
StatefulValuable<num>
negate() that applies a -1 factor to the value
add(num other) that changes the value by adding other to current value
substract(num other) that changes the value by substracting other to current value
multiply(num other) that changes the value by multiplying other with current value
StatefulValuable<int>
increment() add one to current value
decrement() substract one to current value
divide(num other) that changes the value with an integer division by other
StatefulValuable<double>
divide(num other) that changes by dividing current value with other
ValueListenable<T>
toValuable provide a Valuable<T> by calling Valuable<T>.listenable(this)
ValuableWatcher
T def<T>(Valuable<T>? valuable, T defaultValue) that extends the behavior of a ValuableWatcher to be used with a Nullable Valuable
Operations #
ValuableCompare<T>
ValuableNumOperation<Output extends num>
ValuableStringOperation
ValuableSwitch<Switch, Output>
ValuableIf<Output>
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.