Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/179 instance name used to find dep get it dep class #181

59 changes: 48 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ As your App grows, at some point you will need to put your app's logic in classe
But now you need a way to access these objects from your UI code. When I came to Flutter from the .Net world, the only way to do this was the use of InheritedWidgets. I found the way to use them by wrapping them in a StatefulWidget; quite cumbersome and has problems working consistently. Also:

* I missed the ability to easily switch the implementation for a mocked version without changing the UI.
* The fact that you need a `BuildContext` to access your objects made it inaccessible from the Business layer.
* The fact that you need a `BuildContext` to access your objects made it inaccessible from the Business layer.


Accessing an object from anywhere in an App can be done by other ways, but:
Expand All @@ -37,7 +37,7 @@ GetIt is:

### The get_it_mixin

GetIt isn't a state management solution! It's a locator for your objects so you need some other way to notify your UI about changes like `Streams` or `ValueNotifiers`. But together with the [get_it_mixin](https://pub.dev/packages/get_it_mixin) it gets a full featured easy state management solution that integrates with the Objects registered in get_it
GetIt isn't a state management solution! It's a locator for your objects so you need some other way to notify your UI about changes like `Streams` or `ValueNotifiers`. But together with the [get_it_mixin](https://pub.dev/packages/get_it_mixin) it gets a full featured easy state management solution that integrates with the Objects registered in get_it

## Getting Started

Expand Down Expand Up @@ -88,7 +88,7 @@ GetIt getIt = GetIt.instance;
```


> You can use any name you want which makes Brian :smiley: happy like (`sl, backend, services...`) ;-)
> You can use any name you want which makes Brian :smiley: happy like (`sl, backend, services...`) ;-)


Before you can access your objects you have to register them within `GetIt` typically direct in your start-up code.
Expand Down Expand Up @@ -155,7 +155,7 @@ You have to pass a factory function `func` that returns an instance of an implem
### Overwriting registrations

If you try to register a type more than once you will fail with an assertion in debug mode because normally this is not needed and probably a bug.
If you really have to overwrite a registration, then you can by setting the property `allowReassignment==true`.
If you really have to overwrite a registration, then you can by setting the property `allowReassignment==true`.

### Testing if a Singleton is already registered
You can check if a certain Type or instance is already registered in GetIt with:
Expand Down Expand Up @@ -208,7 +208,7 @@ Future<void> reset({bool dispose = true});
```
## Scopes
With V5.0 of GetIt it now supports hierarchical scoping of registration. What does this mean?
You can push a new registration scope like you push a new page on the Navigator. Any registration after that will be registered in this new scope. When accessing an object with `get` GetIt first checks the topmost scope for an registration and then the ones below. This means you can register the same type that was already registered in a lower scope again in a scope above and you will always get the latest registered object.
You can push a new registration scope like you push a new page on the Navigator. Any registration after that will be registered in this new scope. When accessing an object with `get` GetIt first checks the topmost scope for an registration and then the ones below. This means you can register the same type that was already registered in a lower scope again in a scope above and you will always get the latest registered object.

Imagine an app that can be used with or without a login. On App start-up a `DefaultUser` object is registered with the abstract type `User` as singleton. As soon as the user logs in, a new scope is pushed and a new `LoggedInUser` object again with the `User` type is registered that allows more functions. For the rest of the App nothing has changed as it still accesses `User` objects through GetIt.
As soon as the user Logs off all you have to do is pop the Scope and automatically the `DefaultUser` is used again.
Expand All @@ -220,16 +220,16 @@ From V5.0 on you can pass a `dispose` function when registering any Singletons.

```Dart
DisposingFunc<T> dispose
```
where `DisposingFunc` is defined as
```
where `DisposingFunc` is defined as

```Dart
typedef DisposingFunc<T> = FutureOr Function(T param);
```

So you can pass simple and async functions as this parameter. This function is called when you pop or reset the scope or when you reset GetIt completely.

When you push a new scope you can also pass a `dispose` function that is called when a scope is popped or reset but before the dispose functions of the registered objects is called which mean it can still access the objects that were registered in that scope.
When you push a new scope you can also pass a `dispose` function that is called when a scope is popped or reset but before the dispose functions of the registered objects is called which mean it can still access the objects that were registered in that scope.


### Scope functions
Expand Down Expand Up @@ -390,6 +390,15 @@ In case that this services have to be initialized in a certain order because the

When using `dependsOn` you ensure that the registration waits with creating its singleton on the completion of the type defined in `dependsOn`.

The `dependsOn` field also accepts `InitDependency` classes that allow specifying the dependency by type and `instanceName`.

```Dart
getIt.registerSingletonAsync<RestService>(() async => RestService().init(), instanceName:"rest1");

getIt.registerSingletonWithDependencies<AppModel>(
() => AppModelImplmentation(),
dependsOn: [InitDependency(RestService, instanceName:"rest1")]);
```

### Manually signalling the ready state of a Singleton
Sometimes the mechanism of `dependsOn` might not give you enough control. For this case you can use `isReady` to wait for a certain singleton:
Expand Down Expand Up @@ -432,8 +441,8 @@ class ConfigService {
}
}
```
### Using `allReady` repeatedly
Even if you already have awaited `allReady`, the moment you register new async singletons or singletons with dependencies you can use `allReady` again. This makes especially sense if you uses scopes where every scope needs to get initialized.
### Using `allReady` repeatedly
Even if you already have awaited `allReady`, the moment you register new async singletons or singletons with dependencies you can use `allReady` again. This makes especially sense if you uses scopes where every scope needs to get initialized.

### Manual triggering **allReady** (almost deprecated)

Expand Down Expand Up @@ -530,7 +539,35 @@ If you have a mocked version of a Service, you can easily switch between that an
Ok you have been warned! All registration functions have an optional named parameter `instanceName`. Providing a name with factory/singleton here registers that instance with that name and a type. Consequently `get()` has also an optional parameter `instanceName` to access
factories/singletons that were registered by name.

**IMPORTANT:** Each name must be unique per type.
**IMPORTANT:** Each name must be unique per type.


```Dart
abstract class RestService {}
class RestService1 implements RestService{
Future<RestService1> init() async {
Future.delayed(Duration(seconds: 1));
return this;
}
}
class RestService2 implements RestService{
Future<RestService2> init() async {
Future.delayed(Duration(seconds: 1));
return this;
}
}

getIt.registerSingletonAsync<RestService>(() async => RestService1().init(), instanceName : "restService1");
getIt.registerSingletonAsync<RestService>(() async => RestService2().init(), instanceName : "restService2");

getIt.registerSingletonWithDependencies<AppModel>(
() {
RestService restService1 = GetIt.I.get<RestService>(instanceName: "restService1");
return AppModelImplmentation(restService1);
},
dependsOn: [InitDependency(RestService, instanceName:"restService1")],
);
```

### More than one instance of GetIt
While not recommended, you can create your own independent instance of `GetIt`if you don't want to share your locator with some
Expand Down
2 changes: 2 additions & 0 deletions example/lib/code_for_documentation.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:get_it/get_it.dart';

class AppModel {}

class AppModelImplmentation extends AppModel {
Expand Down
12 changes: 12 additions & 0 deletions lib/get_it.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ typedef FactoryFuncParamAsync<T, P1, P2> = Future<T> Function(
P2 param2,
);

/// Data structure used to identify a dependency by type and instanceName
class InitDependency extends Type {
final Type type;
final String? instanceName;

InitDependency(this.type, {this.instanceName});

@override
String toString() => "InitDependency(type:$type, instanceName:$instanceName)";
}

class WaitingTimeOutException implements Exception {
/// In case of an timeout while waiting for an instance to get ready
/// This exception is thrown with information about who is still waiting.
Expand All @@ -59,6 +70,7 @@ class WaitingTimeOutException implements Exception {
this.notReadyYet,
this.areReady,
);

// todo : assert(areWaitedBy != null && notReadyYet != null && areReady != null);

@override
Expand Down
22 changes: 16 additions & 6 deletions lib/get_it_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -788,14 +788,24 @@ class _GetItImplementation implements GetIt {
/// before we start to create itself we use [dependentFutureGroup]
final dependentFutureGroup = FutureGroup();

for (final type in dependsOn!) {
final dependentFactory =
_findFirstFactoryByNameAndTypeOrNull(instanceName, type);
throwIf(dependentFactory == null,
ArgumentError('Dependent Type $type is not registered in GetIt'));
for (final dependency in dependsOn!) {
late final _ServiceFactory<Object, dynamic, dynamic>?
dependentFactory;
if (dependency is InitDependency) {
dependentFactory = _findFirstFactoryByNameAndTypeOrNull(
dependency.instanceName, dependency.type);
} else {
dependentFactory =
_findFirstFactoryByNameAndTypeOrNull(null, dependency);
}
throwIf(
dependentFactory == null,
ArgumentError(
'Dependent Type $dependency is not registered in GetIt'));
throwIfNot(
dependentFactory!.canBeWaitedFor,
ArgumentError('Dependent Type $type is not an async Singleton'),
ArgumentError(
'Dependent Type $dependency is not an async Singleton'),
);
dependentFactory.objectsWaiting.add(serviceFactory.registrationType);
dependentFutureGroup.add(dependentFactory._readyCompleter.future);
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: get_it
description: Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App"
version: 6.1.1
version: 6.1.2
maintainer: Thomas Burkhart (@escamoteur)
authors:
- Flutter Community <community@flutter.zone>
Expand Down
36 changes: 36 additions & 0 deletions test/async_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,42 @@ void main() {
expect(instance, const TypeMatcher<TestClass>());
});

group("dependency", () {
test('Register singleton with dependency and instanceName', () async {
final getIt = GetIt.instance;
await getIt.reset();
getIt.registerSingletonAsync<TestClass>(
() async => TestClass(internalCompletion: false),
);

getIt.registerSingletonAsync<TestClass2>(
() async => TestClass2(internalCompletion: false),
instanceName: "test2InstanceName",
dependsOn: [TestClass]);

await getIt.allReady();
expect(getIt.get<TestClass2>(instanceName: "test2InstanceName"),
isA<TestClass2>());
});

test('Register two dependent singleton with instanceNames', () async {
final getIt = GetIt.instance;
await getIt.reset();
getIt.registerSingletonAsync<TestClass>(
() async => TestClass(internalCompletion: false),
instanceName: "test1InstanceName");

getIt.registerSingletonAsync<TestClass2>(
() async => TestClass2(internalCompletion: false),
instanceName: "test2InstanceName",
dependsOn: [InitDependency(TestClass, instanceName: "test1InstanceName")]);

await getIt.allReady();
expect(getIt.get<TestClass2>(instanceName: "test2InstanceName"),
isA<TestClass2>());
});
});

test('Code for ReadMe', () async {
final sl = GetIt.instance;

Expand Down