Skip to content

Commit 313e32d

Browse files
Merge pull request #12 from MelbourneDeveloper/asynclocking
2.0.0-beta Release Prep
2 parents 9f08c16 + 66cc7ba commit 313e32d

File tree

5 files changed

+87
-18
lines changed

5 files changed

+87
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,6 @@ longer has the @immutable annotation. See the documentation about immutability.
6767
- Upgrade austerity
6868
- Documentation changes and add icon
6969
## 1.0.12
70-
- Fix icon link
70+
- Fix icon link
71+
## 2.0.0-beta
72+
- Async locking. See documentation

README.md

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# ioc_container
22
A lightweight, flexible, and high-performance dependency injection and service location library for Dart and Flutter.
33

4+
Version 2 of the library introduces the groundbreaking [async locking](#v2-and-async-locking) feature for singletons, a feature that's set to revolutionize the way you handle asynchronous initialization in Dart and Flutter! ioc_container is the only known container that offers this feature.
5+
46
![ioc_container](https://github.com/MelbourneDeveloper/ioc_container/raw/main/images/ioc_container-256x256.png)
57

68
![example workflow](https://github.com/MelbourneDeveloper/ioc_container/actions/workflows/build_and_test.yml/badge.svg)
@@ -13,6 +15,8 @@ A lightweight, flexible, and high-performance dependency injection and service l
1315

1416
[Dependency Injection](#dependency-injection-di)
1517

18+
[Version 2 and Async Locking](#v2-and-async-locking)
19+
1620
[Why Use This Library?](#why-use-this-library)
1721

1822
[Performance And Simplicity](#performance-and-simplicity)
@@ -25,8 +29,6 @@ A lightweight, flexible, and high-performance dependency injection and service l
2529

2630
[Scoping and Disposal](#scoping-and-disposal)
2731

28-
[Async Initialization](#async-initialization)
29-
3032
[Testing](#testing)
3133

3234
[Add Firebase](#add-firebase)
@@ -40,6 +42,71 @@ Containers and service locators give you an easy way to lazily create the depend
4042
## Dependency Injection (DI)
4143
[Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) (DI) allows you to decouple concrete classes from the rest of your application. Your code can depend on abstractions instead of concrete classes. It allows you to easily swap out implementations without changing your code. This is great for testing, and it makes your code more flexible. You can use test doubles in your tests, so they run quickly and reliably.
4244

45+
## V2 and Async Locking
46+
47+
Imagine a scenario where you need to initialize a service, like Firebase, connect to a database, or perhaps fetch some initial configuration data. These operations are asynchronous, and in a complex app, there's always a risk of inadvertently initializing the service multiple times, leading to redundant operations, wasted resources, and potential bugs.
48+
49+
Enter async locking: With this feature, you can perform your async initialization with the confidence that it will only ever run once. No matter how many times you request the service, the initialization logic is executed just a single time. This is not just about efficiency; it's about ensuring the consistency and reliability of your services.
50+
51+
Version 2 brings this powerful new feature. This is perfect for initializing Firebase, connecting to a database, or any other async initialization work. You can initialize anywhere in your code and not worry that it might happen again. Furthermore, the singleton never gets added to the container until the initialization completes successfully. This means that you can retry as many times as necessary without the container holding on to a service in an invalid state.
52+
53+
Notice that this example calls the initialization method three times. However, it doesn't run the work three times. It only runs once. The first call to `getAsync()` starts the initialization work. The second and third calls to `getAsync()` wait for the initialization to complete.
54+
55+
```Dart
56+
import 'dart:async';
57+
import 'package:ioc_container/ioc_container.dart';
58+
59+
class ConfigurationService {
60+
Map<String, String>? _configData;
61+
int initCount = 0;
62+
63+
Future<void> initialize() async {
64+
print('Fetching configuration data from remote server...');
65+
// Simulate network delay
66+
await Future<void>.delayed(const Duration(seconds: 2));
67+
_configData = {
68+
'apiEndpoint': 'https://api.example.com',
69+
'apiKey': '1234567890',
70+
};
71+
print('Configuration data fetched!');
72+
initCount++;
73+
}
74+
75+
String get apiEndpoint => _configData!['apiEndpoint']!;
76+
String get apiKey => _configData!['apiKey']!;
77+
}
78+
79+
void main() async {
80+
final builder = IocContainerBuilder()
81+
..addSingletonAsync((container) async {
82+
final service = ConfigurationService();
83+
await service.initialize();
84+
return service;
85+
});
86+
87+
final container = builder.toContainer();
88+
final stopwatch = Stopwatch()..start();
89+
// Multiple parts of the application trying to initialize the service
90+
// simultaneously
91+
final services = await Future.wait([
92+
container.getAsync<ConfigurationService>(),
93+
container.getAsync<ConfigurationService>(),
94+
container.getAsync<ConfigurationService>(),
95+
]);
96+
97+
stopwatch.stop();
98+
99+
print('API Endpoint: ${services.first.apiEndpoint}');
100+
print('API Key: ${services.first.apiKey}');
101+
print('Milliseconds spent: ${stopwatch.elapsedMilliseconds}');
102+
print('Init Count: ${services.first.initCount}');
103+
}
104+
```
105+
106+
You can do initialization work when instantiating an instance of your service. Use `addAsync()` or `addSingletonAsync()` to register the services. When you need an instance, call the `getAsync()` method instead of `get()`.
107+
108+
Check out the [retry package](https://pub.dev/packages/retry) to add resiliency to your app. Check out the [Flutter example](https://github.com/MelbourneDeveloper/ioc_container/blob/f92bb3bd03fb3e3139211d0a8ec2474a737d7463/example/lib/main.dart#L74) that displays a progress indicator until the initialization completes successfully.
109+
43110
## Why Use This Library?
44111
This library makes it easy to
45112
- Easily replace services with mocks for testing
@@ -51,7 +118,7 @@ This library makes it easy to
51118
- It's standard. It aims at being a standard dependency injector so anyone who understands DI can use this library.
52119

53120
### Performance and Simplicity
54-
This library is objectively fast and holds up to comparable libraries in terms of performance. See the [benchmarks](https://github.com/MelbourneDeveloper/ioc_container/tree/main/benchmarks) project and results.
121+
This library is objectively fast and holds up to comparable libraries in terms of performance. These [benchmarks](https://github.com/MelbourneDeveloper/ioc_container/tree/main/benchmarks) are currently out of data for v2 beta but new benchmarks and performance options are coming.
55122

56123
The [source code](https://github.com/MelbourneDeveloper/ioc_container/blob/main/lib/ioc_container.dart) is a fraction of the size of similar libraries and has no dependencies. According to [codecov](https://app.codecov.io/gh/melbournedeveloper/ioc_container), it weighs in at 81 lines of code, which makes it the lightest container I know about. It is stable and has 100% test coverage. At least three apps in the stores use this library in production.
57124

@@ -75,7 +142,7 @@ This will add a line like this to your package's `pubspec.yaml` (and run an impl
75142

76143
```yaml
77144
dependencies:
78-
ioc_container: ^1.0.9 ## Or, latest version
145+
ioc_container: ^2.0.0-beta ## Or, latest version
79146
```
80147
81148
## Getting Started
@@ -116,8 +183,11 @@ void main() {
116183
final builder = IocContainerBuilder()
117184
//The app only has one AuthenticationService for the lifespan of the app (Singleton)
118185
..addSingletonService(AuthenticationService())
119-
//We mint a new UserService/ProductService for each usage
120-
..add((container) => UserService(container<AuthenticationService>()))
186+
//We create a new UserService/ProductService for each usage
187+
..add((container) => UserService(
188+
//This is shorthand for container.get<AuthenticationService>()
189+
container<AuthenticationService>()
190+
))
121191
..add((container) => ProductService());
122192

123193
// Build the container
@@ -296,13 +366,6 @@ The main function creates a scope to retrieve the `UserRepository` from the scop
296366

297367
*Note: all services in the scoped container exist for the lifespan of the scope. They act in a way that is similar to singletons, but when we call `dispose()` on the scope, it calls `dispose()` on each service registration.*
298368

299-
## Async Initialization
300-
You can do initialization work when instantiating an instance of your service. Use `addAsync()` or `addSingletonAsync()` to register the services. When you need an instance, call the `getAsync()` method instead of `get()`.
301-
302-
_Warning: if you get a singleton with `getAsync()` and the call fails, the singleton will always return a `Future` with an error for the lifespan of the container._ You may need to take extra precautions by wrapping the initialization in a try/catch and using a retry. You may need to eventually cancel the operation if retrying fails. For this reason, you should probably scope the container and only use the result in your main container once it succeeds.
303-
304-
Check out the [retry package](https://pub.dev/packages/retry) to add resiliency to your app. Check out the [Flutter example](https://github.com/MelbourneDeveloper/ioc_container/blob/f92bb3bd03fb3e3139211d0a8ec2474a737d7463/example/lib/main.dart#L74) that displays a progress indicator until the initialization completes successfully.
305-
306369
```dart
307370
import 'package:ioc_container/ioc_container.dart';
308371
@@ -550,6 +613,8 @@ extension FlutterFireExtensions on IocContainerBuilder {
550613
//These factories are all async because we need to ensure that Firebase is initialized
551614
addSingletonAsync(
552615
(container) {
616+
//This is typically done at the start of the main() function.
617+
//Be aware that this is being done to ensure that the Flutter engine is initialized before Firebase and never occurs twice
553618
WidgetsFlutterBinding.ensureInitialized();
554619
555620
return Firebase.initializeApp(

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ packages:
8989
path: ".."
9090
relative: true
9191
source: path
92-
version: "1.0.12"
92+
version: "2.0.0-beta"
9393
js:
9494
dependency: transitive
9595
description:

lib/ioc_container.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ class IocContainer {
7373
///so the container can store scope or singletons
7474
final Map<Type, Object> singletons;
7575

76-
// ignore: strict_raw_type, avoid_field_initializers_in_const_classes
76+
///🔒 Map of locks by type. This ensures that no async singletons execute
77+
///more than once, unless there is an error
78+
// ignore: strict_raw_type
7779
final Map<Type, AsyncLock> locks;
7880

7981
///⌖ If true, this container is a scoped container. Scoped containers never

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: ioc_container
22
description: A lightweight, flexible, and high-performance dependency injection and service location library for Dart and Flutter
3-
version: 1.0.12
3+
version: 2.0.0-beta
44
repository: https://github.com/MelbourneDeveloper/ioc_container
55

66
environment:
7-
sdk: ">=2.17.0 <3.0.0"
7+
sdk: ">=2.17.0 <4.0.0"
88

99
dev_dependencies:
1010
austerity: ^1.1.0

0 commit comments

Comments
 (0)