Skip to content

Instantly share code, notes, and snippets.

@escamoteur
Last active January 12, 2020 14:10
Show Gist options
  • Save escamoteur/c4c717518729a4c24a4ce95c10111473 to your computer and use it in GitHub Desktop.
Save escamoteur/c4c717518729a4c24a4ce95c10111473 to your computer and use it in GitHub Desktop.
GetIt supports since recently a mechanic to make initialization of registered
Instances easier.
The idea is that you can mark Singletons/LazySingletons that they will signal
when they are ready.
You then can wait for either that all are ready or that one of them is ready or
trigger the readyFuture manually
In case of a timeout you get detailed debug information.
/// Returns a Future that is completed once all registered Singletons have signaled that they are ready
/// Or when the global [signalReady] is called without an instance
/// [timeOut] if this is set and the future wasn't completed within that time period an
Future<void> allReady({Duration timeOut});
/// Returns a Future that is completed when a given registered Singletons has signaled that it is ready
/// [T] Type of the Singleton to be waited for
/// [instance] registered instance to be waited for
/// [instanceName] Singleton to be waited for that was registered by name instead of a type.
/// You should only use one of the
/// [timeOut] if this is set and the future wasn't completed within that time period an
/// [callee] optional parameter which makes debugging easier. Pass `this` in here.
Future<void> isReady<T>(
{Object instance, String instanceName, Duration timeOut, Object callee});
/// if [instance] is `null` and no singleton is waiting to be signaled this will complete the future you got
/// from [allReady]
///
/// If [instance] has a value GetIt will search for the responsible singleton and complete all futures you might
/// have received by calling [isReady]
/// Typically this is use in this way inside the registered objects init method `GetIt.instance.signalReady(this);`
/// If all waiting singletons/factories have signaled ready the future you can get from [allReady] is automatically completed
///
/// Both ways are mutual exclusive meaning either only use the global `signalReady()` and don't register a singlton/fatory as signaling ready
/// Or let indiviual instance signal their ready state on their own.
void signalReady([Object instance]);
/// In case of an timeout while waiting for an instance to signal ready
/// This exception is thrown whith information about who is still waiting
‎‎​class WaitingTimeOutException implements Exception {
/// if you pass the [callee] parameter to [isReady]
/// this maps lists which callees is waiting for whom
final Map<Type, Type> isWaitingFor;
/// Lists with Types that are still not ready
final List<Type> notSignaledYet;
/// Lists with Types that are already ready
final List<Type> hasSignaled;
WaitingTimeOutException(
this.isWaitingFor, this.notSignaledYet, this.hasSignaled)
: assert(isWaitingFor != null &&
notSignaledYet != null &&
hasSignaled != null);
@override
String toString() {
print(
'GetIt: There was a timeout while waiting for an instance to signal ready');
print('The following instance types where waiting for completion');
for (var entry in isWaitingFor.entries) {
print('${entry.key} is waiting for ${entry.value}');
}
print('The following instance types have NOT signaled ready yet');
for (var entry in notSignaledYet) {
print('$entry');
}
print('The following instance types HAVE signaled ready yet');
for (var entry in hasSignaled) {
print('$entry');
}
return super.toString();
}
}
@escamoteur
Copy link
Author

Thanks!
Hmm, what would be the advantage to passing a bool value?

@Kavantix
Copy link

Kavantix commented Dec 6, 2019

That the class decides if the bool is true or not.
So if the class changes you don't have to also change the register since it will stay the same

@Lootwig
Copy link

Lootwig commented Dec 6, 2019

@Kavantix

void registerSingleton<T extends GetItSingleton>(T instance, {String instanceName});

(note the added "extends" restriction)?

@escamoteur
Copy link
Author

escamoteur commented Jan 12, 2020

I implemented a different mechanism which almost automatically signals that an instance is ready by passing async builder functions to new async registration functions.
See: https://pub.dev/packages/get_it/versions/4.0.0-beta3

But I'm really unsure because when integrating this system in my current app the result looks not as easy to read and is more cumbersome. Compare

old:

  backend.registerSingleton<ErrorReporter>(reporter);

  backend.registerSingleton<SystemService>(
    SystemServiceImplementation(),
  );

  backend.registerSingleton<ConfigService>(
    ConfigServiceImplementation(),
    signalsReady: true,
  );
  backend.registerSingleton<RTSAService>(
    RTSAServiceImplementation(),
    signalsReady: true,
  );
  backend.registerSingleton<MapTileService>(
    MapTileServiceImplementation(),
    signalsReady: false, // check if this is necessary outside of rtsa_map
  );

  backend.registerLazySingleton<InteractionService>(
    () => InteractionServiceImplementation(signalsReady: false),
  );

  backend.registerLazySingleton<GeoLocationService>(
      () => GeoLocationServiceImplementation());

  backend.registerSingleton<DetectionManager>(
    DetectionManager(),
    signalsReady: false,
  );
  backend.registerSingleton<MapManager>(
    MapManager(),
    signalsReady: true,
  );
 ///and later:

  backend<ConfigService>().loadAppConfig().then((config) {
    backend<ErrorReporter>().setServerConfig(config.serverAdress,config.port);
    backend<RTSAService>().init(config.serverAdress, config.port);
    backend<MapTileService>().init(config.serverAdress, config.port);

    backend<GeoLocationService>().init(
        useFixedUserPosition: config.useFixedUserPosition ?? false,
        fixedUserPosition:
            LatLng(config.fixedUserLatitude ?? 0, config.fixedUserLongitude ?? 0));
    backend<DetectionManager>().init(appConfig: config);

    backend<MapManager>().init(
      useCustomMap: config.useCustomMap,
      customMapName: config.customMapName,
    );
  });

to the new Version without separate Initialization:

  backend.registerSingletonAsync<ConfigService>(
      (_) => ConfigServiceImplementation()..loadAppConfig());

  backend.registerSingletonAsync<ErrorReporter>((_) async {
    var config = backend<ConfigService>().currentConfig;
    return reporter
      ..setServerConfig(
        config.serverAdress,
        config.port,
      );
  }, dependsOn: [ConfigService]);

  backend.registerSingletonAsync<RTSAService>(
      (_) async => RTSAServiceImplementation(),
      dependsOn: [ConfigService]);

  backend.registerSingletonAsync<MapTileService>((_) async {
    var config = backend<ConfigService>().currentConfig;
    return MapTileServiceImplementation()
      ..init(
        config.serverAdress,
        config.port,
      );
  }, dependsOn: [ConfigService]);

  backend.registerSingletonAsync<InteractionService>(
    (completer) => InteractionServiceImplementation(completer),
  );

  backend.registerSingletonAsync<GeoLocationService>((_) async {
    var config = backend<ConfigService>().currentConfig;
    return GeoLocationServiceImplementation()
      ..init(
          useFixedUserPosition: config.useFixedUserPosition ?? false,
          fixedUserPosition: LatLng(
              config.fixedUserLatitude ?? 0, config.fixedUserLongitude ?? 0));
  }, dependsOn: [ConfigService]);

  backend.registerSingletonAsync<DetectionManager>(
      (_) async => DetectionManager(),
      dependsOn: [ConfigService]);
  backend.registerSingletonAsync<MapManager>((_) async => MapManager(),
      dependsOn: [ConfigService]);

So maybe back to the drawing board and pick the best of both worlds.

@Kavantix @Lootwig
The idea of having to implement a certain interface is interesting but I'm not sure if we limit the way a registered instance is inherited. Also haveing signalsReady at the same place as the registration makes it easier to see which of them might act asynchronously.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment