Skip to content

Commit

Permalink
Delete addSingletonService
Browse files Browse the repository at this point in the history
  • Loading branch information
MelbourneDeveloper committed Jan 7, 2024
1 parent 36970fa commit 8151725
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 35 deletions.
103 changes: 99 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Version 2 of the library introduces the groundbreaking [async locking](#v2-and-a

[Inspired By .NET](#inspired-by-net)

[Extension Methods](#extension-methods)

## Introduction

Containers and service locators give you an easy way to lazily create the dependencies that your app requires. As your app grows in complexity, you will find that static variables or global factories start to become cumbersome and error-prone. Containers give you a consistent approach to managing the lifespan of your dependencies and make it easy to replace services with mocks for testing. ioc_container embraces the [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) pattern, and offers an approach that is standard across programming languages and frameworks. The implementation of this approach transcends Dart or Flutter. It is a proven and reliable method employed by developers across various technologies for well over a decade.
Expand Down Expand Up @@ -182,7 +184,7 @@ void main() {
// Create a container builder and register your services
final builder = IocContainerBuilder()
//The app only has one AuthenticationService for the lifespan of the app (Singleton)
..addSingletonService(AuthenticationService())
..addSingleton((container) => AuthenticationService())
//We create a new UserService/ProductService for each usage
..add((container) => UserService(
//This is shorthand for container.get<AuthenticationService>()
Expand All @@ -205,7 +207,7 @@ void main() {
}
```

We define the services: `AuthenticationService`, `UserService`, and `ProductService`. Then, we create an `IocContainerBuilder` and register these services using [`addSingletonService()`](https://pub.dev/documentation/ioc_container/latest/ioc_container/IocContainerBuilder/addSingletonService.html) and [`add()`](https://pub.dev/documentation/ioc_container/latest/ioc_container/IocContainerBuilder/add.html) methods. You can also use the [`addSingleton()`](https://pub.dev/documentation/ioc_container/latest/ioc_container/IocContainerBuilder/addSingleton.html) method to add singletons. Finally, we build the container and retrieve the services to use them in our application like this: `container<ProductService>()`.
We define the services: `AuthenticationService`, `UserService`, and `ProductService`. Then, we create an `IocContainerBuilder` and register these services using [`addSingleton()`](https://pub.dev/documentation/ioc_container/latest/ioc_container/IocContainerBuilder/addSingleton.html) and [`add()`](https://pub.dev/documentation/ioc_container/latest/ioc_container/IocContainerBuilder/add.html) methods. Finally, we build the container and retrieve the services to use them in our application like this: `container<ProductService>()`.

## Flutter
You can use ioc_container as a service locator by declaring a global instance and using it anywhere. This is a good alternative to get_it. You can access it inside or outside the widget tree. Or, you can use the [flutter_ioc_container](https://pub.dev/packages/flutter_ioc_container) package to add your container to the widget tree as an [`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html). This is a good alternative to Provider, which can get complicated when you need to manage the lifecycle of your services or replace services for testing.
Expand Down Expand Up @@ -242,7 +244,7 @@ class InventoryService {
// Create a builder so we can replace dependencies later
final IocContainerBuilder builder = IocContainerBuilder(allowOverrides: true)
..addSingletonService(NotificationService())
..addSingleton((container) => NotificationService())
..add((container) => OrderService())
..addSingleton((container) => InventoryService());
Expand Down Expand Up @@ -692,4 +694,97 @@ If you have any further issues, see the [FlutterFire documentation](https://fire

## Inspired By .NET

This library takes inspiration from DI in [.NET MAUI](https://learn.microsoft.com/en-us/dotnet/architecture/maui/dependency-injection) and [ASP .NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-6.0). You register your dependencies with the `IocContainerBuilder` which is a bit like [`IServiceCollection`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection?view=dotnet-plat-ext-7.0) in ASP.NET Core. Then you build it with the `toContainer()` method, which is like the [`BuildServiceProvider()`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollectioncontainerbuilderextensions.buildserviceprovider?view=dotnet-plat-ext-6.0) method in ASP.NET Core. DI is an established pattern on which the whole .NET ecosystem and many other ecosystems depend. This library does not reinvent the wheel, it just makes it easy to use in Flutter and Dart.
This library takes inspiration from DI in [.NET MAUI](https://learn.microsoft.com/en-us/dotnet/architecture/maui/dependency-injection) and [ASP .NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-6.0). You register your dependencies with the `IocContainerBuilder` which is a bit like [`IServiceCollection`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection?view=dotnet-plat-ext-7.0) in ASP.NET Core. Then you build it with the `toContainer()` method, which is like the [`BuildServiceProvider()`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollectioncontainerbuilderextensions.buildserviceprovider?view=dotnet-plat-ext-6.0) method in ASP.NET Core. DI is an established pattern on which the whole .NET ecosystem and many other ecosystems depend. This library does not reinvent the wheel, it just makes it easy to use in Flutter and Dart.

## Extension Methods

Much of the functionality comes from extension methods. Extension methods are better because they don't pollute the core public interface. It is very easy to implement your own `IocContainer` because it only has 4 properties. You can add as many extension methods as you need. The library doesn't come with extensions that are not necessary.

For example, in version V1, there was a `addSingletonService` extension. This was removed in V2 because it is not necessary, but you can easily add it back for backwards compatibility or convenience. This is the extension:

```dart
///Add a singleton service to the container.
void addSingletonService<T>(T service) => addServiceDefinition(
ServiceDefinition<T>(
(container) => service,
isSingleton: true,
),
);
```

### Keyed Services

You may need to use keys to store multiple instances of the same type. You can use extensions to implement this functionality. This example demonstrates how to use extensions to add keyed services to ioc_container

```dart
import 'package:ioc_container/ioc_container.dart';
import 'package:test/test.dart';
///Example service
class BigService {
final String name;
BigService(this.name);
Future<void> callApi() => Future<void>.delayed(Duration(seconds: 1));
///We can check equality by the name(key)
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is BigService && other.name == name;
}
@override
int get hashCode => name.hashCode;
}
///These give us the functionality to add or
///access a service by key
extension KeyedExtensions on IocContainer {
T? keyedService<T>(String key) => get<Map<String, T>>()[key];
void setServiceByKey<T>(String key, T service) =>
get<Map<String, T>>()[key] = service;
}
void main() {
test('Keyed Services', () {
var count = 0;
final builder = (IocContainerBuilder()
..add((container) {
//Increments the name (key) of the service so they are unique
//Uuid would be better
count++;
var bigService = BigService(count.toString());
bigService;
container.setServiceByKey(count.toString(), bigService);
return bigService;
})
..addSingleton(
(container) => <String, BigService>{},
));
final container = builder.toContainer();
final bigContainerOne = container<BigService>();
final bigContainerTwo = container<BigService>();
final bigContainerThree = container<BigService>();
//Verifies the three names
expect(bigContainerOne.name, '1');
expect(bigContainerTwo.name, '2');
expect(bigContainerThree.name, '3');
//Verifies you can access these by key
expect(container.keyedService<BigService>(bigContainerOne.name),
bigContainerOne);
expect(container.keyedService<BigService>(bigContainerTwo.name),
bigContainerTwo);
expect(container.keyedService<BigService>(bigContainerThree.name),
bigContainerThree);
});
}
```
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ThemeNotifier extends ChangeNotifier {

///Configure the services
final IocContainerBuilder builder = IocContainerBuilder(allowOverrides: true)
..addSingletonService(SettingsService())
..addSingleton((c) => SettingsService())
..addSingleton(
(container) => ThemeNotifier(container<SettingsService>()),
);
Expand Down
2 changes: 1 addition & 1 deletion example/macos/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
4 changes: 2 additions & 2 deletions example/test/widget_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ void main() {
group('Theme Switcher App', () {
setUp(() {
//Replace the existing settings service
builder.addSingletonService<SettingsService>(
FakeSettingsService(isLightMode: false));
builder.addSingleton<SettingsService>(
(c) => FakeSettingsService(isLightMode: false));

serviceLocator = builder.toContainer();
});
Expand Down
8 changes: 0 additions & 8 deletions lib/ioc_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,6 @@ class IocContainerBuilder {
<Type, AsyncLock>{},
);

///Add a singleton service to the container.
void addSingletonService<T>(T service) => addServiceDefinition(
ServiceDefinition<T>(
(container) => service,
isSingleton: true,
),
);

///1️⃣ Add a singleton factory to the container. The container
///will only call this once throughout the lifespan of the container
void addSingleton<T>(
Expand Down
36 changes: 18 additions & 18 deletions test/ioc_container_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ void main() {
test('Basic Singleton 2', () {
final a = A('a');
final builder = IocContainerBuilder()
..addSingletonService(a)
..addSingleton((c) => a)
..add((i) => B(a));
final container = builder.toContainer();
expect(container.get<B>().a, a);
Expand All @@ -107,7 +107,7 @@ void main() {
test('Without Scoping', () {
final a = A('a');
final builder = IocContainerBuilder()
..addSingletonService(a)
..addSingleton((c) => a)
..add((i) => B(i<A>()))
..add((i) => C(i<B>()))
..add((i) => D(i<B>(), i.get<C>()));
Expand All @@ -122,7 +122,7 @@ void main() {
test('With Scoping', () {
final a = A('a');
final builder = IocContainerBuilder()
..addSingletonService(a)
..addSingleton((c) => a)
..add((i) => B(i.get<A>()))
..add((i) => C(i.get<B>()))
..add((i) => D(i.get<B>(), i.get<C>()));
Expand All @@ -140,7 +140,7 @@ void main() {
test('With Scoping 2', () {
final a = A('a');
final builder = IocContainerBuilder()
..addSingletonService(a)
..addSingleton((c) => a)
..add((i) => B(i.get<A>()))
..add((i) => C(i.get<B>()))
..add((i) => D(i.get<B>(), i.get<C>()));
Expand All @@ -157,7 +157,7 @@ void main() {
test('With Scoping And Disposing', () async {
final a = A('a');
final builder = IocContainerBuilder()
..addSingletonService(a)
..addSingleton((c) => a)
..add((i) => B(i.get<A>()))
..add<C>(
(i) => C(i.get<B>()),
Expand All @@ -184,7 +184,7 @@ void main() {

test('Named Key Factory', () {
final builder = IocContainerBuilder()
..addSingletonService(AFactory())
..addSingleton((container) => AFactory())
..add((container) => SomeService(container.get<AFactory>()));

final container = builder.toContainer();
Expand All @@ -195,7 +195,7 @@ void main() {

test('Test Singleton', () {
final builder = IocContainerBuilder()
..addSingletonService(A('a'))
..addSingleton((c) => A('a'))
..add((cont) => B(cont.get<A>()));
final container = builder.toContainer();
final a = container.get<A>();
Expand All @@ -214,7 +214,7 @@ void main() {
test('Test Singleton 3', () {
final builder = IocContainerBuilder()
..addSingleton((c) => B(c.get<A>()))
..addSingletonService(A('a'));
..addSingleton((c) => A('a'));
final container = builder.toContainer()..initializeSingletons();
expect(container.singletons[A], container.get<A>());
expect(container.singletons[B], container.get<B>());
Expand All @@ -233,8 +233,8 @@ void main() {

test('Test Can Replace', () {
final builder = IocContainerBuilder(allowOverrides: true)
..addSingletonService(A('a'))
..addSingletonService(A('b'));
..addSingleton((c) => A('a'))
..addSingleton((c) => A('b'));
final container = builder.toContainer();
final a = container.get<A>();
expect(a.name, 'b');
Expand All @@ -243,16 +243,16 @@ void main() {
test('Test Cant Replace', () {
expect(
() => IocContainerBuilder()
..addSingletonService(A('a'))
..addSingletonService(A('b')),
..addSingleton((c) => A('a'))
..addSingleton((c) => A('b')),
throwsException,
);
});

test('Test Readme', () {
final a = A('a');
final builder = IocContainerBuilder()
..addSingletonService(a)
..addSingleton((c) => a)
..add((i) => B(i.get<A>()))
..add((i) => C(i.get<B>()))
..add((i) => D(i.get<B>(), i.get<C>()));
Expand All @@ -263,7 +263,7 @@ void main() {
});

test('Test Immutability', () {
final builder = IocContainerBuilder()..addSingletonService(A('a'));
final builder = IocContainerBuilder()..addSingleton((c) => A('a'));
final container = builder.toContainer();

expect(
Expand Down Expand Up @@ -291,7 +291,7 @@ void main() {
});

test('Test Lazy', () {
final builder = IocContainerBuilder()..addSingletonService(A('a'));
final builder = IocContainerBuilder()..addSingleton((c) => A('a'));
final container = builder.toContainer();
expect(container.singletons.length, 0);
final a = container.get<A>();
Expand All @@ -300,15 +300,15 @@ void main() {
});

test('Test Zealous', () {
final builder = IocContainerBuilder()..addSingletonService(A('a'));
final builder = IocContainerBuilder()..addSingleton((c) => A('a'));
final container = builder.toContainer()..initializeSingletons();
expect(container.singletons.length, 1);
final a = container.get<A>();
expect(container.singletons[A] == a, true);
});

test('Test Is Lazy Before And After', () {
final builder = IocContainerBuilder()..addSingletonService(A('a'));
final builder = IocContainerBuilder()..addSingleton((c) => A('a'));
final container = builder.toContainer();
expect(container.singletons.length, 0);
final a = container.get<A>();
Expand Down Expand Up @@ -851,7 +851,7 @@ void main() {

test('Test Extending For Immutability', () {
final a = A('a');
final builder = IocContainerBuilder()..addSingletonService(a);
final builder = IocContainerBuilder()..addSingleton((c) => a);
final immutableContainer = builder.toContainer().toImmutable();

expect(immutableContainer.singletons.length, 1);
Expand Down

0 comments on commit 8151725

Please sign in to comment.