Skip to content

Instantly share code, notes, and snippets.

@escamoteur
Last active January 12, 2021 11:56
Show Gist options
  • Save escamoteur/97283e8602fdc4ccc6e2b670aba06e83 to your computer and use it in GitHub Desktop.
Save escamoteur/97283e8602fdc4ccc6e2b670aba06e83 to your computer and use it in GitHub Desktop.
language proposal

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 whereof 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

  1. add a callId keyword that returns a unique value for every call position in the code.
  2. add a new type of variable static local that keep their value from one call at a calling position to the next
  3. 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.

@escamoteur
Copy link
Author

@mraleph

things (might) start behaving different.
That I exactly what I expect. it should allow to behave differently when called at different places. I'm completely aware that this is a dangerous feature but it would allow library authors to actually make functions more safe when used in a reactive environment.
And as listed above, neither in extension methods, nor in case of Hooks there is a clean way to store the values explicitly.

Do you think it makes sense to post it in the language repo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment