Enable functions to store data between calls / know from where they were called
Hi,
this title might sound quite strange but during the development of functional_listeners and get_it_mixin I found that the reactive nature of flutter creates some new challenges. Specifically I mean that build
functions are getting called on any data change which makes patterns that I used in the past suddenly difficult.
Let's take the following class as an example:
class Model {
ValueNotifier<String> _notifier;
ValueListenable<String> get nonEmpty => _notifier.where((x) => x.isNotEmpty);
ValueListenable nonEmptySafe;
Model() {
nonEmptySafe = _notifier.where((x) => x.isNotEmpty);
}
Future<String> makeRestCall() {
return Future.value('Result from ');
}
}
Defining a getter like nonEmpty
is no problem in classic UI systems that will only call it ones from one place in the code.
where
creates a new special ValueNotifier
that the caller can subscribe to.
If we access this inside a widget like:
ValueListenableBuilder<String>(
valueListenable: widget.model.nonEmpty,
builder: (context, s,_) {
it works fine the first time the build function is called. But if the surrounding context gets rebuild it will lead to a new instance of that ValueNotifier is created while the first one still exists and reacts on data changes of _notifier
.
the only way to prevent this is to assign the result of where
to a normal field or variable outside the build function like it is done with nonEmptySafe
.
Because of the same reason it's not a good idea to call a data tranformer like where
of functional_listeners or rxdart directly in a constructor of a widget like:
ValueListenableBuilder<String>(
valueListenable: widget.model.nonEmpty.map((x)=>x.toUpper),
builder: (context, s,_) {
another place where you see mistakes like that is in combination with FutureBuilder
that gets it's future from a method call into the model layer. If the context rebuilds suddenly the Futurebuilder
wait for a new Future
and not for the one it first got. Like if you call makeRestCall()
in the model above. what you really wanted is that the FutureBuilder will wait for the first call and not that it does a new call because someone higher up the widget tree did a setState.
The problem here is that makeRestCall()
cannot do anything against this because it doesn't know that it is called from the excact same place again.
To solve this we would need a possibility that a function is able to store data at the place where it is called that will persist between consecutive calls or that a function could query the adress from where it is called so that it can differentiate different calls and can store data in a static map for instance.
By this where
could only create a new instance of the ValueNotifier
that it has to return on the very first call and just return the same instance on all successive calls.
I could imagine several approaches
- add a
callId
keyword that returns a unique value for every call position in the code. - add a new type of variable
static local
that keep their value from one call at a calling position to the next - add a special keyword
localStorage<T>
that allows to store one object between calls
(option 1 is my least favorite because it would lead to static or global maps to reall store the data)
Especially extension functions could profit of such a feature as they have no way to add a new field to their target types if they needed to store any data.
Another example that would benefit from this is that current "hacks" like flutter_hooks or my get_it_mixin could be implemented in a clean way. Currently we increment an index everytime one of its functions is called that is reset to 0 at the beginning of the build
function so that we can identify which call position we are at and to store necessary data. this also means that this calls always have to be done in the same sequence and can't have any conditionals in it.
With the proposed feature this wouldn't be a problem anymore because every function call could be completely independ of other.
I'm aware that such a feature should only used were no other option exist, but especially package authors could add safety messures to their functions that make them save to call inside build functions.
Looks like some of the code formatting is off