Search code examples
flutterdartstateflutter-getxstatefulwidget

Understanding how Getx state update with specific id work with SetState((){})


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.


Solution

  • 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();
        }
        //   });
        // }
      }