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

Generated by: LCOV version 2.0-1