preface

Usually the leaders of foreign companies in the work group to send notification messages

I get it.

I got it.

It’s just that when you say “I get it.” it’s more like you understand something that you didn’t understand before, or you may appear impatient with other teasing remarks.

“I got it.” is simply, “I know, I know.”

Today we will introduce the common library get_IT in Flutter

A, the other

In the Dart and Flutter projects, the default way to provide objects/services to a component is through InheritedWidget.

There are also Provider, Singleton, IoC and other methods.

1.1 InheritedWidget

If you want a part or its model to be able to access a service, the component must be a child of an inherited component. This then leads to unnecessary nesting. And strong dependence, poor continuous maintenance.

///Create the data class DataWidget, and then include the child part of the page with the data class DataWidget on the page
///Create DataWidget
class DataWidget extends InheritedWidget {
  DataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);
  final int data;
  static DataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<DataWidget>();
  }
  @override
  bool updateShouldNotify(DataWidget old) {
    return old.data != data;
  }
}

///2, create InheritedWidgetTestRoute. Dart files
class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}
class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return  Center(
      child: DataWidget( // Key: Use DataWidget package
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: Text(DataWidget.of(context)
              .data.toString())
            ),
            RaisedButton(
              child: Text("Increment"),
              // With each click, incrementing the count and then updating the page rendering,DataWidget's data will be updatedonPressed: () => setState(() => ++count), ) ], ), ), ); }}Copy the code

1.2 the Provider

Another way to provide objects/services for components is to use a Provider, which is cumbersome to use.

///Creating a data class`CountNotifier`Then use the data class in the page`CountNotifier`Wrap the Child section.
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Create a Widget that holds the CountNotifier data
    return ChangeNotifierProvider.value(
      value: CountNotifier(),
      child: MaterialApp(
        title: 'Privoder Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: ProvidePage(title: 'Provider Test page '),),); }}class ProvidePage extends StatelessWidget {
  final String title;
  ProvidePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Get the CountNotifier data (easiest way)
    final counter = Provider.of<CountNotifier>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text( 'Press the button to increase the number :', ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: 'Increment',
        child: constIcon(Icons.add), ), ); }}/// Core: Inherited from ChangeNotifier
/// You can put it in a separate class file
class CountNotifier with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  increment() {
    _count++;
    // The core method notifies the UI to refresh and calls the build methodnotifyListeners(); }}Copy the code

1.3 the Singleton and the IoC

We can get the object we want to access anywhere in the App in other ways, but:

  • If you use Singleton, you cannot easily switch the implementation to another (for example, a mock version for unit testing)

  • The IoC container for dependency injection provides similar functionality, but at the cost of slow startup times and poor readability because you don’t know where the magically injected object is coming from. Since most IoC libraries rely on reflection, they cannot be used with Flutter.

1.4 GetIt

In App iterations, as code engineering grows, at some point it becomes necessary to place parts of the App’s logic in classes separate from the widgets. Making widgets free of direct dependencies makes your code better organized and easier to test and maintain. But now you need a way to access these objects from your UI code.

Author Escamoteur borrowed the concept of Service Locator Splat in.NET and developed it in Dart.

The concept of Service Locators, which is a way to decouple the interface (abstract base class) from the concrete implementation, while allowing access to the concrete implementation from anywhere in your App through the interface.

Hence GetIt, from 1.0 to the current 7.x

Second, the introduction

2.1 define

[GetIt] Official introduction

This is a simple Service Locator for Dart and Flutter projects with some additional goodies highly inspired by Splat. It can be used instead of InheritedWidget or Provider to access objects e.g. from your UI.

Typical usage:

  • Accessing service objects like REST API clients or databases so that they easily can be mocked.
  • Accessing View/AppModels/Managers/BLoCs from Flutter Views

V7.0 has some breaking changes Check please check the release notes to see what’s new.

The translation:

GetIt is a simple service locator for the Dart and Flutter projects that contains some splat-inspired additions. It can be used instead of inheritedWidgets or providers such as accessing objects from your user interface.

Typical usage (usage scenarios) :

  • Access service objects such as REST API clients or databases to easily emulate them
  • The View from a trip to the Flutter View/AppModels/Managers/BLoCs

In a nutshell

GetIt is a simple direct service locator that allows you to separate the interface from the concrete implementation and access the concrete implementation from anywhere in the application.

One-sentence summary

GetIt is a toolbox.

2.2 the characteristics of

  • Extremely fast invocation (complexity O(1))
  • Easy to learn/use
  • You don’t clutter your UI tree with special widgets like providers or Redux do to access your data.

Redux’s design idea is simple, just two sentences.

(1) A Web application is a state machine, and views correspond to states one by one.

(2) All state, stored in one object.

Three, use,

3.1 introduced

Pubspec.yaml under a package introduces the framework

dependencies: flutter: sdk: flutter domain_common: path: .. /domain_common res_common: path: .. /res_common provider: ^5.0. 0
  get_it: ^7.13.
Copy the code

3.2 get

GetIt is used in a singleton mode

GetIt getIt = GetIt.instance;
//or
GetIt getIt = GetIt.I;

// Customize a new one
GetIt getIt = GetIt.asNewInstance();
Copy the code

3.3 registered

GetIt locator = GetIt.instance;

// Register the factory Mutou in GetIt
locator.registerFactory<Mutou>(() => MutouImpl());

// Register singletons in GetIt
locator.registerSingleton<FileSystem>(() => LocalFileSystem());
locator.registerLazySingleton<ClientRepository>(() => ClientRepositoryImpl());
Copy the code

3.4 call

GetIt locator = GetIt.instance;
// Call the factory object
var mutouModel = locator.get<Mutou>();

// Call the singleton
var fileSystemSingleton = locator<FileSystem>();
Copy the code

3.5 Registration Mode

There are three common ways to register

GetIt locator = GetIt.instance;

Future<void> setupLocator({bool test = false}) async {
	// registerSingleton is a registerSingleton
	// FileSystem is registered immediately when setupLocator() is called in main.dart
	// Register the singleton to ensure that it is the same every time FileSystem is called
	locator.registerSingleton<FileSystem>(FileSystem());
  // There are extension functions that can pass parameters and can be asynchronous
	//locator.registerFactoryParam
  //locator.registerFactoryAsync
  //locator.registerFactoryParamAsync
  // You can also extend dependencies
  //locator.registerSingletonWithDependencies

	// registerLazySingleton is registerLazySingleton
	// When setupLocator() is called, it is not generated and is not registered until the ClientRepository tool is first called elsewhere
	// registerSingleton ensures that each call to the ClientRepository tool is the same
	locator.registerLazySingleton<ClientRepository>(ClientRepositoryImpl());
  // Asynchronous mode is also supported
  //locator.registerLazySingletonAsync
	
	/ / 3, the factory pattern, use registerFactory/registerFactoryParam/registerFactoryParamAsync
	// When setupLocator() is called, it is not generated and is not registered until the first invocation of the Mutou tool elsewhere
	RegisterFactory (registerFactory (registerFactory), registerFactory (registerFactory))
	Factoryparam = registerFactoryParam = registerFactoryParam = registerFactoryParam
	/ / parameters must function when the factory, the factory function is asynchronous and need to use registerFactoryParamAsync parameters
	locator.registerFactory<Mutou>( () => MutouImpl()) );
	
	locator.registerFactoryParam<MutouPlus,String.int>((str,num) 
  	=> MutouPlusImpl(param1:str, param2: num));
  	
  	locator.registerFactoryParamAsync<MutouPro,String.int>((str,num) 
  	=> MutouProImpl(param1:str, param2: num));

}

Copy the code

3.6 other

// Check whether an object is registered
  bool isRegistered<T extends Object> ({Object? instance, String? instanceName});

// Reset all registered objects
  Future<void> reset({bool dispose = true});

// Draw a circle for V5.0
// Reset within range
Future<void> resetScope({bool dispose = true});


  // All previous registrations are invalid and new ones are created in a new scope
  void pushNewScope({
    void Function(GetIt getIt)? init,
    String? scopeName,
    ScopeDisposeFunc? dispose,
  });


 // Exits within a range, invalidating all objects registered within that range
  Future<void> popScope();

 // If there are more than one scope, then one scope will exit, and all objects registered in the corresponding scope will be invalid
  Future<bool> popScopesTill(String name);

  // Get the current range name
  String? get currentScopeName;


/ / at a certain time to finish all the timeout asynchronous object creation, asynchronous object creation ignorePendingAsyncCreation is whether to ignore
Future<void> allReady({
    Duration? timeout,
    bool ignorePendingAsyncCreation = false});// Whether to complete asynchronous object creation within a specified timeout period
 Future<void> isReady<T extends Object> ({Object? instance,
    String? instanceName,
    Duration? timeout,
    Object? callee,
  });

// Do not be misled by the name, this is also to check whether asynchronous objects are created, because synchronous objects do not need to be checked
 bool isReadySync<T extends Object> ({Object? instance,
    String? instanceName,
  });

/ / check if complete all asynchronous object creation, asynchronous object creation ignorePendingAsyncCreation is whether to ignore
  bool allReadySync([bool ignorePendingAsyncCreation = false]);

// When an asynchronous object is registered, it is manually activated in the init function
/// If you want to use this mechanism you have to pass [signalsReady==true] when registering
/// the Singleton.
/// Typically this is used in this way inside the registered objects init
/// method `GetIt.instance.signalReady(this); `
void signalReady(Object? instance);
Copy the code

Four, the source code

This paper takes GEt_IT: ^7.1.3 as sample analysis

4.1 itself

Itself is a service factory or toolbox class

class _ServiceFactory<T extends Object.P1.P2> {
  final _ServiceFactoryType factoryType;

  // inside the singleton
  final _GetItImplementation _getItInstance;

  late final Type param1Type;
  late final Type param2Type;

  // Store various objects
  /// Because of the different creation methods we need alternative factory functions
  /// only one of them is always set.
  final FactoryFunc<T>? creationFunction;
  final FactoryFuncAsync<T>? asyncCreationFunction;
  finalFactoryFuncParam<T, P1? , P2? >? creationFunctionParam;finalFactoryFuncParamAsync<T, P1? , P2? >? asyncCreationFunctionParam;///  Dispose function that is used when a scope is popped
  final DisposingFunc<T>? disposeFunction;

  /// In case of a named registration the instance name is here stored for easy access
  final String? instanceName;

  /// true if one of the async registration functions have been used
  final bool isAsync;

  /// If a an existing Object gets registered or an async/lazy Singleton has finished
  /// its creation it is stored here
  Object? instance;

  /// the type that was used when registering. used for runtime checks
  late final Type registrationType;

  /// to enable Singletons to signal that they are ready (their initialization is finished)
  late Completer _readyCompleter;

  /// the returned future of pending async factory calls or factory call with dependencies
  Future<T>? pendingResult;

  /// If other objects are waiting for this one
  /// they are stored here
  final List<Type> objectsWaiting = [];

  bool get isReady => _readyCompleter.isCompleted;

  bool getisNamedRegistration => instanceName ! =null;

  String get debugName => '$instanceName : $registrationType';

  bool getcanBeWaitedFor => shouldSignalReady || pendingResult ! =null || isAsync;

  final bool shouldSignalReady;

  _ServiceFactory(
    this._getItInstance,
    this.factoryType, {
    this.creationFunction,
    this.asyncCreationFunction,
    this.creationFunctionParam,
    this.asyncCreationFunctionParam,
    this.instance,
    this.isAsync = false.this.instanceName,
    required this.shouldSignalReady,
    this.disposeFunction,
  }) : assert(! (disposeFunction ! =null&& instance ! =null &&
                instance is Disposable),
            ' You are trying to register type ${instance.runtimeType.toString()} '
            'that implements "Disposable" but you also provide a disposing function') {
    registrationType = T;
    param1Type = P1;
    param2Type = P2;
    _readyCompleter = Completer();
  }
Copy the code

4.2 registered

Take the common example of registerLazySingleton for a look at the source code for the registration

 @override
  void registerLazySingleton<T>(FactoryFunc<T> func, {String instanceName}) {
    _register<T, void.void>(
        type: _ServiceFactoryType.lazy,
        instanceName: instanceName,
        factoryFunc: func,
        isAsync: false,
        shouldSignalReady: false);
  }

  void _register<T, P1, P2>({
    @required _ServiceFactoryType type,
    FactoryFunc<T> factoryFunc,
    FactoryFuncParam<T, P1, P2> factoryFuncParam,
    FactoryFuncAsync<T> factoryFuncAsync,
    FactoryFuncParamAsync<T, P1, P2> factoryFuncParamAsync,
    T instance,
    @required String instanceName,
    @required bool isAsync,
    可迭代<Type> dependsOn,
    @required bool shouldSignalReady,
  }) {
    / /... Parameter judgment is ignored
    final serviceFactory = _ServiceFactory<T, P1, P2>(
      type,
      creationFunction: factoryFunc,
      creationFunctionParam: factoryFuncParam,
      asyncCreationFunctionParam: factoryFuncParamAsync,
      asyncCreationFunction: factoryFuncAsync,
      instance: instance,
      isAsync: isAsync,
      instanceName: instanceName,
      shouldSignalReady: shouldSignalReady,
    );

    if (instanceName == null) {
      _factories[T] = serviceFactory;
    } else {
      _factoriesByName[instanceName] = serviceFactory;
    }

    // The common singleton is immediately created for registration
    if(type == _ServiceFactoryType.constant && ! shouldSignalReady && ! isAsync && (dependsOn? .isEmpty ??false)) {
      serviceFactory.instance = factoryFunc();
      serviceFactory._readyCompleter.complete();
      return;
    }

    // If a singleton is created asynchronously or with other dependencies, check if it depends on another singleton being created before creating it
    if((isAsync || (dependsOn? .isNotEmpty ??false)) &&
        type == _ServiceFactoryType.constant) {
      /// Any client awaiting the completion of this Singleton
      /// Has to wait for the completion of the Singleton itself as well
      /// as for the completion of all the Singletons this one depends on
      /// For this we use [outerFutureGroup]
      /// A `FutureGroup` completes only if it's closed and all contained
      /// Futures have completed
      final outerFutureGroup = FutureGroup();
      Future dependentFuture;

      if(dependsOn? .isNotEmpty ??false) {
        // Wait for the completion of a function that has an asynchronous dependency
        final dependentFutureGroup = FutureGroup();

        for (final type in dependsOn) {
          final dependentFactory = _factories[type];
          throwIf(dependentFactory == null,
              ArgumentError('Dependent Type $type is not registered in GetIt'));
          throwIfNot(dependentFactory.canBeWaitedFor,
              ArgumentError('Dependent Type $type is not an async Singleton'));
          dependentFactory.objectsWaiting.add(serviceFactory.registrationType);
          dependentFutureGroup.add(dependentFactory._readyCompleter.future);
        }
        dependentFutureGroup.close();// The dependent function is complete
        
        dependentFuture = dependentFutureGroup.future;
      } else {// Execute directly without dependencies
        dependentFuture = Future.sync(() {}); 
      }
      outerFutureGroup.add(dependentFuture);

      /// if someone uses getAsync on an async Singleton that has not be started to get created
      /// because its dependent on other objects this doesn't work because [pendingResult] is not set in
      /// that case. Therefore we have to set [outerFutureGroup] as [pendingResult]
      dependentFuture.then((_) {
        Future<T> isReadyFuture;
        if(! isAsync) {// Non-asynchronous, singletons have dependencies
          serviceFactory.instance = factoryFunc();
          isReadyFuture = Future<T>.value(serviceFactory.instance as T);
          if(! serviceFactory.shouldSignalReady) {// The singleton was not created
						// Mark as doneserviceFactory._readyCompleter.complete(); }}else {
          // Asynchronous singleton
          final asyncResult = factoryFuncAsync();

          isReadyFuture = asyncResult.then((instance) {
            serviceFactory.instance = instance;

            if(! serviceFactory.shouldSignalReady && ! serviceFactory.isReady) { serviceFactory._readyCompleter.complete(); }return instance;
          });
        }
        outerFutureGroup.add(isReadyFuture);
        outerFutureGroup.close();
      });

      // Wait for the return result to be the most recent in the outerFutureGroup list
      serviceFactory.pendingResult =
          outerFutureGroup.future.then((completedFutures) {
        return completedFutures.last asT; }); }}Copy the code

4.3 call

To call the get function source analysis

T get<T extends Object> ({String? instanceName,
    dynamic param1,
    dynamic param2,
  }) {
    // Obtain array _findFactoryByNameOrType
    final instanceFactory = _findFactoryByNameAndType<T>(instanceName);

    Object instance = Object; //late
    if(instanceFactory.isAsync || instanceFactory.pendingResult ! =null) {
      /// We use an assert here instead of an `if.. throw` for performance reasons
      assert(
        instanceFactory.factoryType == _ServiceFactoryType.constant ||
            instanceFactory.factoryType == _ServiceFactoryType.lazy,
        "You can't use get with an async Factory of ${instanceName ?? T.toString()}.",);assert(
        instanceFactory.isReady,
        'You tried to access an instance of ${instanceName ?? T.toString()} that is not ready yet',
      );
      instance = instanceFactory.instance!;
    } else {
      // Obtain the corresponding object from the array
      instance = instanceFactory.getObject(param1, param2);
    }

    assert(
      instance is T,
      'Object with name $instanceName has a different type '
      '(${instanceFactory.registrationType.toString()}) than the one that is inferred '
      '(${T.toString()}) where you call it',);return instance as T;
  }

// Obtain array _findFactoryByNameOrType
/// Is used by several other functions to retrieve the correct [_ServiceFactory]
  _ServiceFactory _findFactoryByNameAndType<T extends Object> (String? instanceName, [
    Type? type,
  ]) {
    final instanceFactory =
        _findFirstFactoryByNameAndTypeOrNull<T>(instanceName, type: type);
    assert( instanceFactory ! =null.// ignore: missing_whitespace_between_adjacent_strings
      'Object/factory with ${instanceName ! =null ? 'with name $instanceName and ' : ' '}'
      'type ${T.toString()} is not registered inside GetIt. '
      '\n(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance; '
      '\nDid you forget to register it?) ',);returninstanceFactory! ; }/// Is used by several other functions to retrieve the correct [_ServiceFactory]
_ServiceFactory<T, dynamic.dynamic>?
      _findFirstFactoryByNameAndTypeOrNull<T extends Object> (String? instanceName,
          {Type? type,
          bool lookInScopeBelow = false{})/// We use an assert here instead of an `if.. throw` because it gets called on every call
    /// of [get]
    /// `(const Object() is! T)` tests if [T] is a real type and not Object or dynamic
    assert( type ! =null || const Object(a)is! T,
      'GetIt: The compiler could not infer the type. You have to provide a type '
      'and optionally a name. Did you accidentally do `var sl=GetIt.instance(); ` '
      'instead of var sl=GetIt.instance; ',); _ServiceFactory<T,dynamic.dynamic>? instanceFactory;

    int scopeLevel = _scopes.length - (lookInScopeBelow ? 2 : 1);

  // Quick lookup
    while (instanceFactory == null && scopeLevel >= 0) {
      // Note: factoriesByName = 
      
       >>{};
      ?>
      // factoriesByName this is a Map, so the complexity is O(1))
      final factoryByTypes = _scopes[scopeLevel].factoriesByName[instanceName];
      if (type == null) { instanceFactory = factoryByTypes ! =null
            ? factoryByTypes[T] as _ServiceFactory<T, dynamic.dynamic>?
            : null;
      } else {
        /// in most cases we can rely on the generic type T because it is passed
        /// in by callers. In case of dependent types this does not work as these types
        /// are dynamicinstanceFactory = factoryByTypes ! =null
            ? factoryByTypes[type] as _ServiceFactory<T, dynamic.dynamic>?
            : null;
      }
      scopeLevel--;
    }

    return instanceFactory;
  }

  // Obtain the corresponding object from the array
  /// returns an instance depending on the type of the registration if [async==false]
  T getObject(dynamic param1, dynamic param2) {
    assert(! (factoryType ! = _ServiceFactoryType.alwaysNew && (param1 ! =null|| param2 ! =null)),
        'You can only pass parameters to factories! ');

    try {
      switch (factoryType) {
          // Always create the latest schema
        case _ServiceFactoryType.alwaysNew:
          if(creationFunctionParam ! =null) {
 
            return creationFunctionParam(param1 as P1, param2 as P2);
          } else {
            return creationFunction();
          }
          break;
          // Constant mode
        case _ServiceFactoryType.constant:
          return instance as T;
          break;
          // Lazy mode
        case _ServiceFactoryType.lazy:
          if (instance == null) {
            instance = creationFunction();
            _readyCompleter.complete();
          }
          return instance as T;
          break;
        default:
          throw StateError('Impossible factoryType'); }}catch (e, s) {
      print('Error while creating ${T.toString()}');
      print('Stack trace:\n $s');
      rethrow; }}Copy the code

conclusion

As a commonly used dynamic service locator, GetIt is a toolbox that gets rid of inheritedWidgets, providers, Singleton, IoC and other demanding and cumbersome processes. It is easy to use and stands out.

As the developer of Flutter, it is often used on the surface, but when you calm down and analyze its source code, it has a complete and sound logic.

As a user, you can see things when you see them.

As a profiler, fine taste, do not have a flavor.