-
-
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(); | |
} | |
} |
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.
@Kavantix
(note the added "extends" restriction)?