A dart library make singleton in dart easy and elegant again
MIT License
Singleton is useful pattern, it can help to:
But sometimes, singleton could be a hassle to use, because:
Singleton
library is designed to make those scenario less hassle, enable developer to use Singletons in dart elegantly.
This library majorly supports 3 different singleton usage:
SharedPreferences
's value in Widget build
method.This library is designed with Flutter
in mind, but it doesn't depends on any flutter specific code, so it can be used anywhere dart works.
You might write following code a thousand times:
class MyLazyService {
static MyLazyService _instance;
static MyLazyService get instance {
if (_instance == null) {
_instance = MyLazyService._();
}
return _instance;
}
/// Private constructor
MyLazyService._() {
}
/// do something
void doSomething(){
}
}
MyLazyService.instance.doSomething();
It is working but boring to write, and it has some issues, such as pollutes test.
Singleton
class MyLazyService {
/// Factory method that reuse same instance automatically
factory MyLazyService() => Singleton.lazy(() => MyLazyService._()).instance;
/// Private constructor
MyLazyService._() {}
/// do something
void doSomething() {}
}
MyLazyService().doSomething() // Look like a new instance but it is a singleton.
class MyEagerService {
/// Factory method that reuse same instance automatically
factory MyEagerService() => Singleton<MyEagerService>().instance;
final MyApi api;
/// Constructor create and register new instance
MyEagerService.initialize(this.api) {
// Register current instance
Singleton.register(this);
}
/// do something
void doSomething() {}
}
void main() {
final appSettings = getAppSettings();
final httpClient = createHttpClient(appSetting);
final api = createApi(httpClient);
MyEagerService.initialize(api) // Create and register the the singleton
.doSomething(); // Use the instance
}
MyEagerService().doSomething(); // Use the singleton instance
It could be tricky to deal with the singleton which depends on async resource. Unfortunately in Flutter/Dart, async resources is everywhere.
Here is a close-to-real example how to deal with this case with Singleton library:
Some background types declaration:
class AppSettings {
static Future<AppSettings> loadAppSettings() {
// load app settings from somewhere asynchronously
}
}
class HttpClient {
final AppSettings appSettings;
HttpClient(this.appSettings);
}
Type uses Future singleton
class MyFutureService {
/// Factory method that reuse same instance automatically
factory MyFutureService() => Singleton<MyFutureService>().instance;
static Future<MyFutureService> createInstance() async {
final appSettings = await Singleton<AppSettings>().ensuredInstance();
final httpClient = HttpClient(appSettings);
return MyFutureService._(httpClient);
}
final HttpClient httpClient;
MyFutureService._(this.httpClient);
/// Some method
void doSomething() {}
}
Register future singleton. Singleton.register
understands Future
, which resolves Future
and registered the value of Future
as singleton.
void main() {
// Register AppSettings settings as a future singleton
Singleton.register(AppSettings.loadAppSettings());
// Create and register the the MyService as singleton
Singleton.register(MyFutureService.createInstance());
runApp();
}
Future singleton can be used as other types of singleton,
MyFutureService().doSomething();
But as the singleton is parsed from future, singleton is used before the future resolves, an StateError
says "ingleton is being used before being resolved" would be thrown.
The error can be omit by ensure the instance execute ensure instance creation at check point convenient:
await Singleton.ensureInstanceFor(MyFutureService);
Multiple types can be checked together:
await Singleton.ensureInstanceFor([MyFutureService, AppSettings]);
If error is thrown by the future, the error won't lost, it is rethron when Singleton.ensureInstanceFor
or Singleton.instance
is called.
Singleton could cause unexpected test failure due to they lives across the test boundary. Singletons created by Singleton
library can be cleared via apis that only visible in tests.
setUp((){
// Reset singleton registry before setup environment to avoid potentially pollution
Singleton.resetAllForTest();
Singleton.register(....) //
});
tearDown(() {
Singleton.resetAllForTest(); // Reset singleton registry to avoid singleton pollution
});
Sometimes you might want to check the singleton status for diagnosis purpose. you can achieve it by:
Singleton.debugPrintAll();
Or you only cares about a certain types
Singleton.debugPrintAll(MySingleton);
Singleton.debugPrintAll(Singleton<MySingleton>());
Singleton.debugPrintAll([MySingleton, AnotherSingleton]);
Singleton.debugPrintAll([Singleton<MySingleton>(), Singleton<AnotherSingleton>()]);
Singleton.debugPrintAll([Singleton<MySingleton>(), AnotherSingleton]);
This suppose to be rare, but in some extreme case, if you want to get rid of your singleton. It is possible:
Singleton<MySingleton>().deregister();
The MIT License (MIT)