Flutter looks at the dependency principle of Getx from the source code

Use

Principles

1. Source code analysis of Get's dependency injection

1、 Get.put

Get.put() is used every time we store an object. When you want to use Get.find().
So how does Getx save the objects we need? And can you share data across pages?
Next, go to the source code with questions to find the answers we need.

First, let's take a look at the code when we put

  S put<S>(S dependency,
          {
    
    String? tag,
          bool permanent = false,
          InstanceBuilderCallback<S>? builder}) =>
      GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
extension Inst on GetInterface {
    
    

This code is in an extension class called Inst. He is an extension of GetInterface.

Then let's look at the put method of the GetInstance class

  S put<S>(
    S dependency, {
    
    
    String? tag,
    bool permanent = false,
    @deprecated InstanceBuilderCallback<S>? builder,
  }) {
    
    
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

Seeing that the parameter isSingleton above is true, it means that the put method is always a singleton object. If you don't want a singleton,
you can use the Get.create() method, which will create a new instance object every time you find it. From the code, you can see that isSingleton is false.

  void create<S>(
    InstanceBuilderCallback<S> builder, {
    
    
    String? tag,
    bool permanent = true,
  }) {
    
    
    _insert(
      isSingleton: false,
      name: tag,
      builder: builder,
      permanent: permanent,
    );
  }

Then you can see that the insert method is called again

  void _insert<S>({
    
    
    bool? isSingleton,
    String? name,
    bool permanent = false,
    required InstanceBuilderCallback<S> builder,
    bool fenix = false,
  }) {
    
    
    final key = _getKey(S, name);

    if (_singl.containsKey(key)) {
    
    
      final dep = _singl[key];
      if (dep != null && dep.isDirty) {
    
    
        _singl[key] = _InstanceBuilderFactory<S>(
          isSingleton,
          builder,
          permanent,
          false,
          fenix,
          name,
          lateRemove: dep as _InstanceBuilderFactory<S>,
        );
      }
    } else {
    
    
      _singl[key] = _InstanceBuilderFactory<S>(
        isSingleton,
        builder,
        permanent,
        false,
        fenix,
        name,
      );
    }
  }

Let's look at it sentence by sentence.
First, get the key through name (that is, the tag we passed in) and S (the data type we injected). In general, no tag is set. Therefore, when we use put, then find gets the same object. If the tag is set, the same type but different instance objects will be generated. It is clear from the source code above.

  String _getKey(Type type, String? name) {
    
    
    return name == null ? type.toString() : type.toString() + name;
  }

When the name is empty, the data type of S is directly used as the key, and when it is not empty, the two are directly spliced.
The second line of code is if judgment. Determine whether the key is included in _singl. _singl is a Map object with String as key and _InstanceBuilderFactory as value.

 static final Map<String, _InstanceBuilderFactory> _singl = {
    
    };
  1. If the key does not exist in _singl, directly create a new object and assign it to the current key.
  2. If the key exists in _singl, get the object directly through the key. Then, it is judged that the obtained object is not empty and the object is a dirty object, and then the object with the existing key is reassigned. Otherwise, ignore it.

So what is a dirty object? Looking at the source code below, you can know that when the widget is disposed, the put object will be marked as dirty, and then the delete method will be called back to recycle the marked as dirty object.


  static void reportRouteWillDispose(Route disposed) {
    
    
    final keysToRemove = <String>[];

    _routesKey[disposed]?.forEach(keysToRemove.add);

    /// Removes `Get.create()` instances registered in `routeName`.
    if (_routesByCreate.containsKey(disposed)) {
    
    
      for (final onClose in _routesByCreate[disposed]!) {
    
    
        // assure the [DisposableInterface] instance holding a reference
        // to onClose() wasn't disposed.
        onClose();
      }
      _routesByCreate[disposed]!.clear();
      _routesByCreate.remove(disposed);
    }

    for (final element in keysToRemove) {
    
    
      GetInstance().markAsDirty(key: element);

      //_routesKey.remove(element);
    }

    keysToRemove.clear();
  }
  
  void markAsDirty<S>({
    
    String? tag, String? key}) {
    
    
    final newKey = key ?? _getKey(S, tag);
    if (_singl.containsKey(newKey)) {
    
    
      final dep = _singl[newKey];
      if (dep != null && !dep.permanent) {
    
    
        dep.isDirty = true;
      }
    }
  }

Well, I have finished reading the part of the insert method, which is mainly to store the data. It was later found that he then called the find method.
Generally speaking, we will directly return the inserted object, but Getx has to work hard to find the method. This might feel a little strange. Find out next time.

  S find<S>({
    
    String? tag}) {
    
    
    final key = _getKey(S, tag);
    if (isRegistered<S>(tag: tag)) {
    
    
      final dep = _singl[key];
      if (dep == null) {
    
    
        if (tag == null) {
    
    
          throw 'Class "$S" is not registered';
        } else {
    
    
          throw 'Class "$S" with tag "$tag" is not registered';
        }
      }
      final i = _initDependencies<S>(name: tag);
      return i ?? dep.getDependency() as S;
    } else {
    
    
      // ignore: lines_longer_than_80_chars
      throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
    }
  }

The key is still generated through tag and S. Then there is another if judgment, if the object to be found has been registered.
You can get the registered object through the key. We directly look at the non-empty case. The _initDependencies method is called
Let's look at the source code

  S? _initDependencies<S>({
    
    String? name}) {
    
    
    final key = _getKey(S, name);
    final isInit = _singl[key]!.isInit;
    S? i;
    if (!isInit) {
    
    
      i = _startController<S>(tag: name);
      if (_singl[key]!.isSingleton!) {
    
    
        _singl[key]!.isInit = true;
        if (Get.smartManagement != SmartManagement.onlyBuilder) {
    
    
          RouterReportManager.reportDependencyLinkedToRoute(_getKey(S, name));
        }
      }
    }
    return i;
  }

Determine whether the instance object is initialized for the first time, if not, return the object directly.
If yes, start the controller object initialization operation.

  /// 初始化 controller
  S _startController<S>({
    
    String? tag}) {
    
    
    final key = _getKey(S, tag);
    final i = _singl[key]!.getDependency() as S;
    if (i is GetLifeCycleBase) {
    
    
      i.onStart();
      if (tag == null) {
    
    
        Get.log('Instance "$S" has been initialized');
      } else {
    
    
        Get.log('Instance "$S" with tag "$tag" has been initialized');
      }
      if (!_singl[key]!.isSingleton!) {
    
    
        RouterReportManager.appendRouteByCreate(i);
      }
    }
    return i;
  }

Let's look at this line of code. final i = _singl[key]!.getDependency() as S;
This instance object calls a method getDependency of itself. Let's see what he does?

  S getDependency() {
    
    
    if (isSingleton!) {
    
    
      if (dependency == null) {
    
    
        _showInitLog();
        dependency = builderFunc();
      }
      return dependency!;
    } else {
    
    
      return builderFunc();
    }
  }

The first choice is to judge whether it is a singleton. If you directly create a new object, assign it to the dependency and store it, and you can directly return the object when you find it next time. If not, a new object will be created each time.
Then the next line of code if (i is GetLifeCycleBase)
This if judgment is the key to the life cycle of the controller binding widget. Determine whether the object type of put is a subclass of GetLifeCycleBase. Do I have to inherit a GetxController when I write a controller. Let's take a look at the class inheritance relationship below.

1. abstract class GetxController extends DisposableInterface
    with ListenableMixin, ListNotifierMixin {
    
    }

2. abstract class DisposableInterface extends GetLifeCycle {
    
    }

3. abstract class GetLifeCycle with GetLifeCycleBase {
    
    
  GetLifeCycle() {
    
    
    $configureLifeCycle();
  }
}

From the above inheritance relationship, we can clearly understand that as long as the controller class we built inherits GetxController, it is a subclass of GetLifeCycleBase. Then call the onStart() method, which is mainly to initialize the onInit, onReady, and onClose
of the corresponding life cycle of the controller .

if (!_singl[key]!.isSingleton!) {
    
    
        RouterReportManager.appendRouteByCreate(i);
      }

This sentence means that if it is not a singleton object, this method will only be called for an instance created with Get.create().

  static void appendRouteByCreate(GetLifeCycleBase i) {
    
    
    _routesByCreate[_current] ??= HashSet<Function>();
    // _routesByCreate[Get.reference]!.add(i.onDelete as Function);
    _routesByCreate[_current]!.add(i.onDelete);
  }

The function of this method is to associate resource recovery with routing, and when the widget is disposed, it will call back the onClose method of the controller.
Then look at the code after _startController

        _singl[key]!.isInit = true;
        if (Get.smartManagement != SmartManagement.onlyBuilder) {
    
    
          RouterReportManager.reportDependencyLinkedToRoute(_getKey(S, name));
        }

Set isInit to true, then this code will not be executed next time, only initialized once.
Call the reportDependencyLinkedToRoute method of the RouterReportManager class.
This code is mainly used to associate the controller class with routing.

  • SmartManagement.full This is the default. Destroy classes that are not used and not made permanent. In most cases, you'll want to keep this configuration untouched. If you are using GetX for the first time, then do not change this configuration.

  • SmartManagement.onlyBuilders With this option, only controllers started in init: or loaded into a Binding with Get.lazyPut() will be destroyed.
    If you use Get.put() or Get.putAsync() or any other method, SmartManagement will not have permission to remove this dependency.
    Even
    widgets instantiated with "Get.put " are removed by default behavior, unlike SmartManagement.onlyBuilders.

  • SmartManagement.keepFactory is like SmartManagement.full, it will remove it's dependencies when it is not used anymore, but it will keep their factory, which means if you need that instance again, it will recreate that dependency relation.

2、Get.lazyPut

Comparison of Get.put and Get.lazyPut

  S put<S>(
    S dependency, {
    
    
    String? tag,
    bool permanent = false,
    @deprecated InstanceBuilderCallback<S>? builder,
  }) {
    
    
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

  void lazyPut<S>(
    InstanceBuilderCallback<S> builder, {
    
    
    String? tag,
    bool? fenix,
    bool permanent = false,
  }) {
    
    
    _insert(
      isSingleton: true,
      name: tag,
      permanent: permanent,
      builder: builder,
      fenix: fenix ?? Get.smartManagement == SmartManagement.keepFactory,
    );
  }

Through comparison, it is found that put directly passes in an instance object, and lazyPut is a builder object.
put directly finds and returns an object, but lazyPut does not. Only when you need it, that is, you use Get.find, will create a new instance. Object, so lazy loading is what it means.

3、Get.create

  void create<S>(
    InstanceBuilderCallback<S> builder, {
    
    
    String? tag,
    bool permanent = true,
  }) {
    
    
    _insert(
      isSingleton: false,
      name: tag,
      builder: builder,
      permanent: permanent,
    );
  }

We see that permanent is set to true. Indicates that the object persists.
Let's take a look at the source code below, we can see that it is used in the method of recycling objects, and then look down.
The delete method of the GetInstance class

 bool delete<S>({
    
    String? tag, String? key, bool force = false}) {
    
    
    final newKey = key ?? _getKey(S, tag);

    if (!_singl.containsKey(newKey)) {
    
    
      Get.log('Instance "$newKey" already removed.', isError: true);
      return false;
    }

    final dep = _singl[newKey];

    if (dep == null) return false;

    final _InstanceBuilderFactory builder;
    if (dep.isDirty) {
    
    
      builder = dep.lateRemove ?? dep;
    } else {
    
    
      builder = dep;
    }

    if (builder.permanent && !force) {
    
    
      Get.log(
        // ignore: lines_longer_than_80_chars
        '"$newKey" has been marked as permanent, SmartManagement is not authorized to delete it.',
        isError: true,
      );
      return false;
    }
    final i = builder.dependency;

    if (i is GetxServiceMixin && !force) {
    
    
      return false;
    }

    if (i is GetLifeCycleBase) {
    
    
      i.onDelete();
      Get.log('"$newKey" onDelete() called');
    }

    if (builder.fenix) {
    
    
      builder.dependency = null;
      builder.isInit = false;
      return true;
    } else {
    
    
      if (dep.lateRemove != null) {
    
    
        dep.lateRemove = null;
        Get.log('"$newKey" deleted from memory');
        return false;
      } else {
    
    
        _singl.remove(newKey);
        if (_singl.containsKey(newKey)) {
    
    
          Get.log('Error removing object "$newKey"', isError: true);
        } else {
    
    
          Get.log('"$newKey" deleted from memory');
        }
        return true;
      }
    }
  }

Let's take a look at a piece of code, and we can see that when the permanet is true, the whole returns false, and the subsequent object deletion operation will not be performed. Is it very clear?

    if (builder.permanent && !force) {
    
    
      Get.log(
        // ignore: lines_longer_than_80_chars
        '"$newKey" has been marked as permanent, SmartManagement is not authorized to delete it.',
        isError: true,
      );
      return false;
    }

4、 Get.putAsync

 Future<S> putAsync<S>(
    AsyncInstanceBuilderCallback<S> builder, {
    
    
    String? tag,
    bool permanent = false,
  }) async {
    
    
    return put<S>(await builder(), tag: tag, permanent: permanent);
  }

You can see that it is no different from put except that the builder is set to asynchronous.

2. Summary

From the perspective of source code, the essence of Getx is to use Map to maintain a dependency relationship. The corresponding object can be found by using find.
The last thing I want to say is that being familiar with the source code can help us use the framework better. If it is useful to you, please don't hesitate to give it a like!

Guess you like

Origin blog.csdn.net/hjjdehao/article/details/126511436