You may be thinking “How is Signals different than using ValueNotifier?” and that is a valid question when first coming to signals because at a glance they look very familiar.
But there is more to reactive programming than just the containers for the data. We still need to react to when the data changes which requires us to add listeners. This gets even more complicated the more we add.
As you can see there is a lot to keep track of mentally and you are writing more boilerplate than domain logic.
This also only works for Flutter and not in pure dart applications since ValueNotifier is tied to the Flutter SDK.
The same example above for signals would be the following:
Lines of code are not everything, but this dramatically reduces the boilerplate needed to achieve the same result.
Computed
State is not just about the values updated directly but often the derived state needed for any one screen.
In the example above we had two count values, but what if we had a third that was the total result and checked if it was even or odd.
With ValueNotifier you would have to calculate that directly or create a class with ChangeNotifier and start calling notifyListeners.
This still is possible but not efficient. What we care about is the total and isEven/isOdd result, not the count values themselves. Yet we have to still need to react to them when they change to trigger each computation.
It can be easy to miss an addListener or ValueListenableBuilder if you are unaware of a dependency in the chain.
Of course you could break it out with ChangeNotifier but then you are not using ValueNotifier anymore.
This still recalculates everything on every update. Total/isEven/isOdd are always computed regardless if the value has changed.
But how would this be possible with signals?
There some special things happening here that I want to call out.
Total/isEven/isOdd is only called when the values it depends on change. Each computed signal will store the value and cache it until dependencies change.
If the value is never read the computed callbacks are never called. That means you only calculate the state you use when you use it.
Also the UI logic does not need to care about count1/count2 and only the values you want to read. This leads to fewer mistakes and simpler code.
Incremental Migration
If you have value notifiers you cannot update because they come from a library you can convert them to a signal.
You can also provide a signal as a ValueListenable or ValueNotifier depending on if the signal is read-only or not.
These extensions will also dispose the ValueNotifier and ValueListenable when the signal is disposed.
Outside of Flutter
Signals can be used in pure dart applications. This means you can have the same logic for server side, flutter, CLIs, html web apps and more.
All the other signals in the package are syntax sugar for core types or helper methods to connect to Flutter specifics.
With Signals 0.6.0 you can also create a signal that extends both Signal, ValueNotifier and Stream.