singleton 0.0.2 singleton: ^0.0.2 copied to clipboard
Singleton make singleton implementation less hassle, and also make it more friendly to tests.
singleton #
Why create this library #
Singleton is useful pattern, it can help to:
- instantiate objects lazily to speed up app loading
- instantiate objects on demand, so it reduce memory wasted on unused objects
- object is either complicated or expensive to instantiate, but it is used across the app, so having a singleton is economic option
- object depends on asynchronous resources, but need to be used in synchronous way. So it has been to created before hand.
But sometimes, singleton could be a hassle to use, because:
- Implement lazy pattern every time used is painful and could be avoided.
- Singleton are very unfriendly to unit test, as it lives through the test, could cause unexpected test failure.
- Singleton depends on asynchronous resource could be complicated to manage.
Singleton
library is designed to make those scenario less hassle, enable developer to use Singletons in dart elegantly.
What this library can do #
This library majorly supports 3 different singleton usage:
- Lazy Singleton: Type is created lazily and on demand. It behaves similar to Kotlin lazy delegated property.
- Eager Singleton: Type is complicated to create, or creation depends on resources only available in some cases. So eagerly creating an instance could be good idea. Kotlin object declaration
- Future Singleton: Type depends on async resource to instantiate, but needs to be used in synchronous-enforced environment, such as depends on
SharedPreferences
's value in Widgetbuild
method. - Allow to reset all registered singleton for test, so across test pollution can be mitigated.
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.
Lazy Singleton #
Implement lazy singleton manually #
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.instnace.doSomething();
It is working but boring to write, and it has some issues, such as pollutes test.
Lazy singleton with 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.
Eager 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
Future Singleton #
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();
}
Use Future Singleton #
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]);
Errors #
If error is thrown by the future, the error won't lost, it is rethron when Singleton.ensureInstanceFor
or Singleton.instance
is called.
Support Unit Test #
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
});
Find out what is registered #
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]);
Remove singleton #
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();
License #
The MIT License (MIT)