LCOV - code coverage report
Current view: top level - lib - ioc_container.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 73 88 83.0 %
Date: 2023-05-17 07:08:55 Functions: 0 0 -

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

Generated by: LCOV version 1.16