2

while discovering Getx source code, I faced a piece of code that make me confused, it's not related 100% to Getx. it's about SetState(() {})

so, in the Getx there is a state update that targets only the widgets with a specific id:

Disposer addListenerId(Object? key, GetStateUpdate listener) {
_updatersGroupIds![key] ??= <GetStateUpdate>[];
_updatersGroupIds![key]!.add(listener);
return () => _updatersGroupIds![key]!.remove(listener);

}

the _updatersGroupIds is a HashMap<Object?, List<void Function()>>?.

and when calling it from the StatefulWidget, like this :

controller?.addListenerId(
        widget.id,
        setState(() {}),
      );

the id is passed from a property in the widget call, and as you see it's passing the whole SetState(() {}) of that StatefulWidget in the function!

so the _updatersGroupIds will be something like this :

_updatersGroupIds == {
  "idExample": [
    SetState(() {}),
  ]
};

right?

what confused me is when we try to update the state we call the update method from the controller with desirables ids to update :

update(["idExample"]);

this is implemented as follows:

   void update([List<Object> ids]) {
    for (final id in ids) {
      if (_updatersGroupIds!.containsKey(id)) {
        final listGroup = _updatersGroupIds![id]!;
        for (var item in listGroup) {
          item();
        }
      }
    }
  }

so what I understand is that when we get the "exampleId" in the _updatersGroupIds hashmap, we iterate over all the SetState(() {}) in that list, by calling them

so what I'm expecting vs what happens :

what I'm expecting: that since we called all the SetState(() {}) in that List, it will update the State of all StateFulWidget in the whole app, or at least it will update the last StatefulWidget we run that function from.

what is happening: it updates only the StatefulWidget with the same "exampleId", and nothing else

my question is: how when we store the SetState(() {}) of multiple widgets like :

List<void Function()> staterList = [SetState(() {}), SetState(() {}), SetState(() {})];

when we call the second one:

staterList[1].call();

how does it know that it should update only the StatefulWidget where that SetState((){}) came from and nothing else?

another format of question: is there some referencing set on SetState(() {}), so that it knows the StateFulWidget where it came from when we call it somewhere outside?

please share your knowledge with me.

Gwhyyy
  • 7,554
  • 3
  • 8
  • 35

1 Answers1

3

ListNotifierSingle
lib/get_state_manager/src/simple/list_notifier.dart: class ListNotifierSingle = ListNotifier with ListNotifierSingleMixin; we care mostly about ListNotifierSingleMixin

ListNotifierSingleMixin
lib/get_state_manager/src/simple/list_notifier.dart: mixin ListNotifierSingleMixin on Listenable is referenced in _updatersGroupIds

GetStateUpdate
lib/get_state_manager/src/simple/list_notifier.dart: typedef GetStateUpdate = void Function(); This is basically the same as setState, you could put any function that is void with no parameters, but that may produce undesired results.

_updatersGroupIds
Your definition of _updatersGroupIds is close, but incorrect. _updatersGroupIds is a Object reference key, mapped to a reference to a ListNotifierSingleMixin. _updatersGroupIds would be

{
  Object():  ListNotifierSingle(), // references to instances of each object
  Object():  ListNotifierSingle(), 
}

addListenerId
When running the below code, an Object reference key, and ListNotifierSingle reference are added to the _updatersGroupIds. Then the instance of ListNotifierSingle has it's addListener function run, which adds the setState function to the ListNotifierSingle instance's _updaters. See the below functions with comments

controller?.addListenerId(
        widget.id,
        setState(() {}),
      );
Disposer addListenerId(Object? key, GetStateUpdate listener) {

  // init if is null
  _updatersGroupIds![key] ??= ListNotifierSingle();

  // pass listener (setState) to ListNotifierSingle reference addListener 
  // function, this is defined in ListNotifierSingleMixin
  return _updatersGroupIds![key]!.addListener(listener);
}
@override
Disposer addListener(GetStateUpdate listener) {
  // no disposed
  assert(_debugAssertNotDisposed());
  // add setState to list
  _updaters!.add(listener);
  // return a function that removes setState from list (_updaters) when called
  return () => _updaters!.remove(listener);
}


update
lib/get_state_manager/src/simple/get_controllers.dart: void update([List<Object>? ids, bool condition = true]), which is part of abstract class GetxController extends ListNotifier with GetLifeCycleMixin, which extends ListNotifier, which extends ListNotifierGroupMixin; therefore, refreshGroup is defined under ListNotifierGroupMixin.

When calling update(["idExample"]); all passed ids are refreshed using refreshGroup in update, see below.

  void update([List<Object>? ids, bool condition = true]) {
    // not called in our example
    if (!condition) {
      return;
    }
    // not called in our example
    if (ids == null) {
      refresh();
      // called in our example, defined under ListNotifierGroupMixin
      // all passed ids are refreshed using refreshGroup
    } else {
      for (final id in ids) {
        refreshGroup(id);
      }
    }
  }

refreshGroup calls _notifyGroupUpdate

  void refreshGroup(Object id) {
    assert(_debugAssertNotDisposed());
    _notifyGroupUpdate(id);
  }

_notifyGroupUpdate checks if passed id is in _updatersGroupIds, and if it is it runs _notifyUpdate function for the single instance of ListNotifierSingleMixin with the key id in the _updatersGroupIds map.

  void _notifyGroupUpdate(Object id) {
    // check if exists
    if (_updatersGroupIds!.containsKey(id)) {
      // run _updatersGroupIds function for specified 
      // ListNotifierSingleMixin instance
      _updatersGroupIds![id]!._notifyUpdate();
    }
  }

_notifyUpdate iterates over all the functions in _updaters, which are for a single instance of ListNotifierSingleMixin, which in our case is the one with key "idExample". There should only be one function in _updaters in our example, which is the setState function of the widget in our example. There will only be multiple values in _updaters if you call controller?.addListenerId with the same id more than once.

  void _notifyUpdate() {
    // if (_microtaskVersion == _version) {
    //   _microtaskVersion++;
    //   scheduleMicrotask(() {
    //     _version++;
    //     _microtaskVersion = _version;

    // make sure list is not null
    final list = _updaters?.toList() ?? [];

    //  iterate over functions in _updaters queue
    // when the function is run it removes it's contained function
    // from _updaters, see addListener. The inner function should be 
    // setState
    for (var element in list) {
      element();
    }
    //   });
    // }
  }
CatTrain
  • 214
  • 2
  • 5
  • 1
    Hi CatTrain, thank you so mush for your answer, it really worth reading, but what I want to understand is why when we iterate over the _updaters?.toList() which will run specific SetState((){}) s updates only the widget where it comes the first place, I mean since we just call it, how it knows and filters which widget to update and not the other widgets – Gwhyyy Nov 12 '22 at 15:25
  • 1
    because like I see, they are all the same SetState((){]), how calling one does update only a specific widget and not all of them ? – Gwhyyy Nov 12 '22 at 15:27
  • 1
    and again, thank you so mush for responding, I will really appreciate if you could guide me on the way this mechanism works – Gwhyyy Nov 12 '22 at 15:28
  • 1
    @Gwhyyy "because like I see, they are all the same SetState((){])", this is false. When the `setState` functions are added to `_updaters` it is a reference to a specific `State`'s `setState` function. The `setState` functions are called by reference. All languages use references at some level, you should read about them. [Reference (C++)](https://en.wikipedia.org/wiki/Reference_(C%2B%2B)) – CatTrain Nov 12 '22 at 17:07
  • 1
    Oh, so every SetState(() {}) has a pointer to the State object of StatefulWidget, and when we call a specific one, it updates the State object where it points – Gwhyyy Nov 12 '22 at 20:35
  • I can understand it now, before accepting this as the right answer, may I ask to include this in the answer? so it will be on the big screen for everyone who asks the same question. – Gwhyyy Nov 12 '22 at 20:38
  • 1
    @Gwhyyy I would like to emphasize a reference, and not a pointer. [What are the differences between a pointer variable and a reference variable?](https://stackoverflow.com/questions/57483/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable) – CatTrain Nov 13 '22 at 23:46