Async objects
Asynchronous Factories
If a factory needs to call an async function you can use registerFactoryAsync()
/// [T] type to register
/// [func] factory function for this type
/// [instanceName] if you provide a value here your factory gets registered with that
/// name instead of a type. This should only be necessary if you need to register more
/// than one instance of one type.
void registerFactoryAsync<T>(FactoryFuncAsync<T> func, {String instanceName});
To access instances created by such a factory you can't use get()
but you have to use getAsync()
so that you can await the creation of the requested new instance.
/// Returns a Future of an instance that is created by an async factory or a Singleton that is
/// not ready with its initialization.
Future<T> getAsync<T>([String instanceName]);
Asynchronous Singletons
Additionally, you can register asynchronous Singletons which means Singletons that have an initialization that requires async function calls. To be able to control such asynchronous start-up behaviour GetIt supports mechanisms to ensure the correct initialization sequence.
You create a Singleton with an asynchronous creation function
void registerSingletonAsync<T>(FactoryFuncAsync<T> factoryfunc,
{String instanceName,
Iterable<Type> dependsOn,
bool signalsReady = false});
The difference to a normal Singleton is that you don't pass an existing instance but provide a factory function that returns a Future
that completes at the end of factoryFunc
and signals that the Singleton is ready to use unless true
is passed for signalsReady
. (see next chapter) To synchronize with other "async Singletons" you can pass a list of Type
s in dependsOn
that have to be ready before the passed factory is executed.
There are two ways to signal the system that an instance is ready.
Synchronizing asynchronous initializations of Singletons
Often your registered services need to do asynchronous initialization work before they can be used from the rest of the app. As this is such a common task, and it's closely related to registration/initialization GetIt supports you here too.
GetIt
has the function allReady
which returns Future<void>
that can be used e.g. with a Flutter FutureBuilder to await that all asynchronous initialization is finished.
Future<void> allReady({Duration timeout, bool ignorePendingAsyncCreation = false});
There are different approaches to how the returned Future can be completed:
Using async Singletons
If you register any async Singletons allReady
will complete only after all of them have completed their factory functions. Like:
class RestService {
Future<RestService> init() async {
Future.delayed(Duration(seconds: 1));
return this;
}
}
final getIt = GetIt.instance;
/// in your setup function:
getIt.registerSingletonAsync<ConfigService>(() async {
final configService = ConfigService();
await configService.init();
return configService;
});
getIt.registerSingletonAsync<RestService>(() async => RestService().init());
// here we asume an async factory function `createDbServiceAsync`
getIt.registerSingletonAsync<DbService>(createDbServiceAsync);
/// ... in your startup page:
return FutureBuilder(
future: getIt.allReady(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Scaffold(
body: Center(
child: Text('The first real Page of your App'),
),
);
} else {
return CircularProgressIndicator();
}
});
The above example shows you different ways to register async Singletons. The start-up page will display a CircularProgressIndicator
until all services have been created.
Solving dependencies
Automatic using dependsOn
In a case, these services have to be initialized in a certain order because they depend on that other services are already ready to be used you can use the dependsOn
parameter of registerFactoryAsync
. If you have a non-async Singleton that depends on other Singletons, there is registerSingletonWithDependencies
. In the following example, DbService
depends on ConfigService
, and AppModel
depends on ConfigService
and RestService
getIt.registerSingletonAsync<ConfigService>(() async {
final configService = ConfigService();
await configService.init();
return configService;
});
getIt.registerSingletonAsync<RestService>(() async => RestService().init());
getIt.registerSingletonAsync<DbService>(createDbServiceAsync,
dependsOn: [ConfigService]);
getIt.registerSingletonWithDependencies<AppModel>(
() => AppModelImplmentation(),
dependsOn: [ConfigService, DbService, RestService]);
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
.
getIt.registerSingletonAsync<RestService>(() async => RestService().init(), instanceName:"rest1");
getIt.registerSingletonWithDependencies<AppModel>(
() => AppModelImplmentation(),
dependsOn: [InitDependency(RestService, instanceName:"rest1")]);
Manually signaling 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:
/// Returns a Future that completes if the instance of a Singleton, defined by Type [T] or
/// by name [instanceName] or passing the an existing [instance], is ready
/// If you pass a [timeout], a [WaitingTimeOutException] will be thrown if the instance
/// is not ready in the given time. The Exception contains details on which Singletons are
/// not ready at that time.
/// [callee] optional parameter which makes debugging easier. Pass `this` in here.
Future<void> isReady<T>({
Object instance,
String instanceName,
Duration timeout,
Object callee,
});
To signal that a singleton is ready it can use signalReady
, provided you have set the optional signalsReady
parameter when registering it OR make your registration type implement the empty abstract class WillSignalReady
. Otherwise, allReady
will wait on a call to signalsReady. No automatic signaling will happen in that case.
/// Typically this is used in this way inside the registered objects init
/// method `GetIt.instance.signalReady(this);`
void signalReady(Object instance);
You can use this to initialize your Singletons without async registration by using fire and forget async function from your constructors like so:
class ConfigService {
ConfigService()
{
init();
}
Future init() async {
// do your async initialisation...
GetIt.instance.signalReady(this);
}
}
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 use scopes where every scope needs to get initialized.
Manual triggering allReady (almost deprecated)
By calling signalReady(null)
on your GetIt
instance the Future
you can get from allReady
will be completed. This is the most basic way to synchronize your start-up. If you want to do that don't use signalsReady
or async Singletons!!! I recommend using one of the other ways because they are more flexible and express your intention more clear.
You can find here a detailed blog post on async factories and startup synchronization