Closed saibotma closed 1 year ago
I would not say that this is an antipattern, it's just something that is harder to do for service locators/dependency injections. I thought about this a lot and I also had this issue a couple of times. Here are some thoughts about this:
In most cases, my dependency took care of handling the async stuff by themselves to avoid dealing with this complexity in the dependency injection.
For example, we have a Repository
that works with Hive
which is initialized asynchronously. In my case, the Repository
will initialize Hive
maybe by using a callback.
Just some pseudo code:
injector.registerSingleton(() {
// openbox is async.
return Respository(initHive: () => Hive.openbox());
});
This is not an elegant solution if you have the use case a lot because it requires you to handle that init stuff in every class again.
Let's say you register a class that comes from another framework where you have no control if the creation is async or not. You can create a simple wrapper around that dependency. Something like an "AsyncContainer".
// implementation is skipped.
class AsyncContainer<T> {
Future<T> get value;
AsyncContainer(Future<T> Function() factory);
}
injector.registerDependency<AsyncContainer<MyClass>>(() {
return AsyncContainer(() => MyClass.createInstanceAsync(userService: injector.get()));
});
final container = await injector.get<AsyncContainer<MyClass>>().value;
This solution is also not that elegant because you now have to make sure you get all your async dependencies using AsyncContainer
.
injector
We probably need something like
injector.registerSingletonAsync<T>(/* factory method */)
,
injector.registerDependencyAsync<T>(/* factory method */)
or
injector.registerAsyncFactory<T>(/* factory method */)
When getting the dependency you have to handle the case that the dependency is not yet initialized. So we need something like Future<T> getAsync<T>()
and if the dependency that you try to get here is not async we have to throw. (or the other way around if you use get<T>()
and it's not async).
GetIt
has other functions like GetIt.allReady()
which waits until all async factories are resolved. I think this is really dangerous because if you sometimes register an async dependency that takes longer to initialize and you call GetIt.ready()
before hiding a splash screen or even before starting your flutter app, you will probably block the user. A better solution for this would be to handle this explicitly because the interface of that dependency force you to do this which brings me back to the first point. " >Dependency takes care of the async initializations
Example:
injector.registerSingletonAsync(() async {
final myService = MyService();
await myService.init();
return myService;
});
await injector.waitUntilAllDependenciesAreReady();
runApp(MyApp());
Maybe it makes sense to show a loading indicator to your users and call MyService.init
at some point in the future. Maybe only then when you need it which might be when the user opens a specific screen. This can be controlled by whatever class is using MyService
. Maybe a redux action, maybe a Bloc or a Provider.
Architecture wise there is a lot that can be done wrong with async dependencies, but it's up to the consumer of the package to decide this. I am down for implementing this into this package because many users asking for this.
To inject async dependencies I used this wrapper which acts as a factory to build the dependency.
The extra static sync
method I used in my tests.
typedef AsyncBuilder<T> = Future<T> Function();
typedef NonAsyncBuilder<T> = T Function();
class AsyncProvider<T> {
AsyncBuilder<T> builder;
AsyncProvider(this.builder);
Future<T> get get => builder();
static AsyncProvider<T> sync<T>(NonAsyncBuilder<T> builder) {
return AsyncProvider<T>(() async => builder());
}
}
i.e.
injector.registerSingleton<AsyncProvider<UserService>>(() => AsyncProvider<UserService>(() => UserService.get()));
class MyClass {
final AsyncProvider<UserService> userServiceProvider;
MyClass() : this.userServiceProvider = Injector.appInstance.get<AsyncProvider<UserService>>();
Future<void> doSomething() async {
final UserService userService = await userServiceProvider.get;
...
}
}
Maybe this is an antipattern, however i once had the use case where i wanted to register a dependency by using an async dependency builder:
Unfortunately, this does not work at the moment. I am open for discussing this topic.