LCOV - code coverage report
Current view: top level - lib/lib - ioc_container.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 100.0 % 92 92
Test Date: 2023-08-29 19:51:57 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:async';
       2              : 
       3              : import 'package:ioc_container/async_lock.dart';
       4              : 
       5              : ///❌ An exception that occurs when the service is not found
       6              : class ServiceNotFoundException<T> implements Exception {
       7              :   ///❌ Creates a new instance of [ServiceNotFoundException]
       8            1 :   const ServiceNotFoundException(this.message);
       9              : 
      10              :   ///The exception message
      11              :   final String message;
      12            1 :   @override
      13            2 :   String toString() => 'ServiceNotFoundException: $message';
      14              : }
      15              : 
      16              : ///📙 Defines a factory for the service and whether or not it is a singleton.
      17              : class ServiceDefinition<T> {
      18              :   ///📙 Defines a factory for the service and whether or not it is a singleton.
      19            2 :   const ServiceDefinition(
      20              :     this.factory, {
      21              :     this.isSingleton = false,
      22              :     this.dispose,
      23              :     this.disposeAsync,
      24              :   })  : assert(
      25            2 :           !isSingleton || dispose == null,
      26              :           'Singleton factories cannot have a dispose method',
      27              :         ),
      28              :         assert(
      29            2 :           dispose == null || disposeAsync == null,
      30              :           "Service definitions can't have both dispose and disposeAsync",
      31              :         );
      32              : 
      33              :   ///1️⃣ If true, only one instance of the service will be created and shared
      34              :   ///for the lifespan of the container.
      35              :   final bool isSingleton;
      36              : 
      37              :   ///🏭 The factory that creates the instance of the service and can access
      38              :   ///other services in this container
      39              :   final T Function(
      40              :     IocContainer container,
      41              :   ) factory;
      42              : 
      43              :   ///🗑️ The dispose method that is called when you dispose the scope
      44              :   final void Function(T service)? dispose;
      45              : 
      46              :   ///🗑️ The async dispose method that is called when you dispose the scope
      47              :   final Future<void> Function(T service)? disposeAsync;
      48              : 
      49            3 :   void _dispose(T instance) => dispose?.call(instance);
      50              : 
      51            3 :   Future<void> _disposeAsync(T instance) async => disposeAsync?.call(instance);
      52              : }
      53              : 
      54              : ///📦 A built Ioc Container. To create a new [IocContainer], use
      55              : ///[IocContainerBuilder]. To get a service from the container, call
      56              : ///[get], or [getAsync]
      57              : ///Call [scoped] to get a scoped container
      58              : class IocContainer {
      59              :   ///📦 Creates an IocContainer. You can build your own container by injecting
      60              :   ///service definitions and singletons here, but you should probably use
      61              :   ///[IocContainerBuilder] instead.
      62            2 :   const IocContainer(
      63              :     this.serviceDefinitionsByType,
      64              :     this.singletons,
      65              :     this.locks, {
      66              :     this.isScoped = false,
      67              :   });
      68              : 
      69              :   ///📙 The service definitions by type
      70              :   final Map<Type, ServiceDefinition<dynamic>> serviceDefinitionsByType;
      71              : 
      72              :   ///1️⃣ Map of singletons or scoped services by type. This map is mutable
      73              :   ///so the container can store scope or singletons
      74              :   final Map<Type, Object> singletons;
      75              : 
      76              :   // ignore: strict_raw_type, avoid_field_initializers_in_const_classes
      77              :   final Map<Type, AsyncLock> locks;
      78              : 
      79              :   ///⌖ If true, this container is a scoped container. Scoped containers never
      80              :   ///create more than one instance of a service
      81              :   final bool isScoped;
      82              : 
      83              :   ///👐 Get an instance of the service by type
      84            2 :   T get<T extends Object>() {
      85            4 :     final serviceDefinition = serviceDefinitionsByType[T];
      86              : 
      87              :     if (serviceDefinition == null) {
      88            1 :       throw ServiceNotFoundException<T>(
      89            1 :         'Service $T not found',
      90              :       );
      91              :     }
      92              : 
      93            4 :     if (serviceDefinition.isSingleton || isScoped) {
      94            4 :       final singletonValue = singletons[T];
      95              : 
      96              :       if (singletonValue != null) {
      97              :         return singletonValue as T;
      98              :       }
      99              :     }
     100              : 
     101            4 :     final service = serviceDefinition.factory(this) as T;
     102              : 
     103            4 :     if (serviceDefinition.isSingleton || isScoped) {
     104            4 :       singletons[T] = service;
     105              :     }
     106              : 
     107              :     return service;
     108              :   }
     109              : 
     110              :   ///👐 This is a shortcut for [get]
     111            2 :   T call<T extends Object>() => get<T>();
     112              : }
     113              : 
     114              : ///👷 A builder for creating an [IocContainer].
     115              : class IocContainerBuilder {
     116              :   ///👷 Creates a container builder
     117            2 :   IocContainerBuilder({this.allowOverrides = false});
     118              :   final Map<Type, ServiceDefinition<dynamic>> _serviceDefinitionsByType = {};
     119              : 
     120              :   /// 🔃 Throw an error if a service is added more than once. Set this to true
     121              :   /// when you want to add mocks to set of services for a test.
     122              :   final bool allowOverrides;
     123              : 
     124              :   ///📙 Add a factory to the container.
     125            2 :   void addServiceDefinition<T>(
     126              :     ///Add a factory and whether or not this service is a singleton
     127              :     ServiceDefinition<T> serviceDefinition,
     128              :   ) {
     129            4 :     if (_serviceDefinitionsByType.containsKey(T)) {
     130            1 :       if (allowOverrides) {
     131            2 :         _serviceDefinitionsByType.remove(T);
     132              :       } else {
     133            1 :         throw Exception('Service already exists');
     134              :       }
     135              :     }
     136              : 
     137            6 :     _serviceDefinitionsByType.putIfAbsent(T, () => serviceDefinition);
     138              :   }
     139              : 
     140              :   ///📦 Create an [IocContainer] from the [IocContainerBuilder].
     141            4 :   IocContainer toContainer() => IocContainer(
     142            2 :         Map<Type, ServiceDefinition<dynamic>>.unmodifiable(
     143            2 :           _serviceDefinitionsByType,
     144              :         ),
     145            2 :         <Type, Object>{},
     146              :         // ignore: strict_raw_type
     147            2 :         <Type, AsyncLock>{},
     148              :       );
     149              : 
     150              :   ///Add a singleton service to the container.
     151            2 :   void addSingletonService<T>(T service) => addServiceDefinition(
     152            1 :         ServiceDefinition<T>(
     153            1 :           (container) => service,
     154              :           isSingleton: true,
     155              :         ),
     156              :       );
     157              : 
     158              :   ///1️⃣ Add a singleton factory to the container. The container
     159              :   ///will only call this once throughout the lifespan of the container
     160            2 :   void addSingleton<T>(
     161              :     T Function(
     162              :       IocContainer container,
     163              :     ) factory,
     164              :   ) =>
     165            2 :       addServiceDefinition<T>(
     166            2 :         ServiceDefinition<T>(
     167            4 :           (container) => factory(container),
     168              :           isSingleton: true,
     169              :         ),
     170              :       );
     171              : 
     172              :   ///🏭 Add a factory to the container.
     173            2 :   void add<T>(
     174              :     T Function(
     175              :       IocContainer container,
     176              :     ) factory, {
     177              :     void Function(T service)? dispose,
     178              :   }) =>
     179            2 :       addServiceDefinition<T>(
     180            2 :         ServiceDefinition<T>(
     181            4 :           (container) => factory(container),
     182              :           dispose: dispose,
     183              :         ),
     184              :       );
     185              : 
     186              :   ///⌛ Adds an async [ServiceDefinition]
     187            1 :   void addAsync<T>(
     188              :     Future<T> Function(
     189              :       IocContainer container,
     190              :     ) factory, {
     191              :     Future<void> Function(T service)? disposeAsync,
     192              :   }) =>
     193            1 :       addServiceDefinition<Future<T>>(
     194            1 :         ServiceDefinition<Future<T>>(
     195            2 :           (container) async => factory(container),
     196            2 :           disposeAsync: (service) async => disposeAsync?.call(await service),
     197              :         ),
     198              :       );
     199              : 
     200              :   ///1️⃣ ⌛ Add an async singleton factory to the container. The container
     201              :   ///will only call the factory once throughout the lifespan of the container
     202            1 :   void addSingletonAsync<T>(
     203              :     Future<T> Function(
     204              :       IocContainer container,
     205              :     ) factory,
     206              :   ) =>
     207            1 :       addServiceDefinition<Future<T>>(
     208            1 :         ServiceDefinition<Future<T>>(
     209              :           isSingleton: true,
     210            2 :           (container) async => factory(container),
     211              :         ),
     212              :       );
     213              : }
     214              : 
     215              : ///Extensions for IocContainer
     216              : extension IocContainerExtensions on IocContainer {
     217              :   ///🗑️ Dispose all singletons or scope. Warning: don't use this on your root
     218              :   ///container. You should only use this on scoped containers.
     219            1 :   Future<void> dispose() async {
     220            1 :     assert(isScoped, 'Only dispose scoped containers');
     221            3 :     for (final type in singletons.keys) {
     222              :       //Note: we don't need to check if the service is a singleton because
     223              :       //singleton service definitions never have dispose
     224            2 :       final serviceDefinition = serviceDefinitionsByType[type]!;
     225              : 
     226              :       //We can't do a null check here because if a Dart issue
     227            4 :       serviceDefinition._dispose.call(singletons[type]);
     228              : 
     229            3 :       await serviceDefinition._disposeAsync(singletons[type]);
     230              :     }
     231            2 :     singletons.clear();
     232              :   }
     233              : 
     234              :   ///🏁 Initalizes and stores each singleton in case you want a zealous
     235              :   ///container instead of a lazy one
     236            1 :   void initializeSingletons() {
     237            3 :     serviceDefinitionsByType.forEach((type, serviceDefinition) {
     238            1 :       if (serviceDefinition.isSingleton) {
     239            2 :         singletons.putIfAbsent(
     240              :           type,
     241            3 :           () => serviceDefinition.factory(
     242              :             this,
     243              :           ) as Object,
     244              :         );
     245              :       }
     246              :     });
     247              :   }
     248              : 
     249              :   ///⌖ Gets a service, but each service in the object mesh will have only one
     250              :   ///instance. If you want to get multiple scoped objects, call [scoped] to
     251              :   ///get a reusable [IocContainer] and then call [get] or [getAsync] on that.
     252            3 :   T getScoped<T extends Object>() => scoped().get<T>();
     253              : 
     254              :   ///⌖ Creates a new Ioc Container for a particular scope. Does not use existing
     255              :   ///singletons/scope by default. Warning: if you use the existing singletons,
     256              :   ///calling [dispose] will dispose those singletons
     257            2 :   IocContainer scoped({
     258              :     bool useExistingSingletons = false,
     259              :   }) =>
     260            2 :       IocContainer(
     261            2 :         serviceDefinitionsByType,
     262            4 :         useExistingSingletons ? Map<Type, Object>.from(singletons) : {},
     263            2 :         {},
     264              :         isScoped: true,
     265              :       );
     266              : 
     267              :   ///⌛ Gets a service that requires async initialization. Add these services
     268              :   ///with [IocContainerBuilder.addAsync] or
     269              :   ///[IocContainerBuilder.addSingletonAsync]. You can only use this on factories
     270              :   ///that return a Future<>.
     271            1 :   Future<T> getAsync<T>([
     272              :     Duration? timeout = const Duration(minutes: 5),
     273              :   ]) async {
     274            2 :     final serviceDefinition = serviceDefinitionsByType[Future<T>];
     275              : 
     276              :     if (serviceDefinition == null) {
     277            1 :       throw ServiceNotFoundException<T>(
     278            1 :         'Service $T not found',
     279              :       );
     280              :     }
     281              : 
     282            2 :     if (serviceDefinition.isSingleton || isScoped) {
     283            2 :       final singletonValue = singletons[Future<T>];
     284              : 
     285              :       if (singletonValue != null) {
     286              :         //Return completed successful future
     287              :         return singletonValue as Future<T>;
     288              :       }
     289              : 
     290            2 :       if (!locks.containsKey(T)) {
     291              :         //Add a lock
     292            2 :         locks[T] =
     293            4 :             AsyncLock<T>(() => serviceDefinition.factory(this) as Future<T>);
     294              :       }
     295              : 
     296            2 :       final lock = locks[T]! as AsyncLock<T>;
     297              : 
     298              :       try {
     299              :         //Await the locked call
     300            1 :         final future = timeout != null ? lock.execute(timeout) : lock.execute();
     301              :         await future;
     302              : 
     303              :         //Store successful future
     304            2 :         singletons[Future<T>] = future;
     305              : 
     306              :         return future;
     307              :       } finally {
     308              :         //Remove the lock
     309            2 :         locks.remove(T);
     310              :       }
     311              :     }
     312              : 
     313            2 :     return serviceDefinition.factory(this) as Future<T>;
     314              :   }
     315              : 
     316              :   ///⛙ Merge the singletons or scope from a container into this container. This
     317              :   ///only moves singleton definitions by default, but you can override this
     318              :   ///with [mergeTest]
     319            1 :   void merge(
     320              :     IocContainer container, {
     321              :     bool overwrite = false,
     322              :     bool Function(
     323              :       Type type,
     324              :       ServiceDefinition<dynamic>? serviceDefinition,
     325              :       Object? singleton,
     326              :     )? mergeTest,
     327              :   }) {
     328            3 :     for (final key in container.singletons.keys.where(
     329              :       mergeTest != null
     330            2 :           ? (type) => mergeTest(
     331              :                 type,
     332            2 :                 serviceDefinitionsByType[type],
     333            2 :                 container.singletons[type],
     334              :               )
     335            4 :           : (type) => serviceDefinitionsByType[type]?.isSingleton ?? false,
     336            1 :     )) {
     337              :       if (overwrite) {
     338            4 :         singletons[key] = container.singletons[key]!;
     339              :       } else {
     340            5 :         singletons.putIfAbsent(key, () => container.singletons[key]!);
     341              :       }
     342              :     }
     343              :   }
     344              : }
        

Generated by: LCOV version 2.0-1