Skip to content

Async objects

Asynchronous Factories

If a factory needs to call an async function you can use registerFactoryAsync()

dart
/// [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.

dart
/// 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

dart
  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 Types 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.

dart
  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:

dart
  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

dart
  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.

dart
  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:

dart
  /// 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.

dart
/// 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:

dart
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

Released under the MIT License.