A Singleton ensures only one instance of a class exists in your app.
It’s useful for shared services like logging, analytics, or database access.
In Flutter, you can build a singleton using Dart’s language features without extra packages.
Approach A: Simple static instance
The easiest way is to create a static instance inside the class.
class AppLogger {
static final AppLogger _instance = AppLogger._internal();
factory AppLogger() => _instance;
AppLogger._internal(); // Private constructor
void log(String message) {
print('LOG: $message');
}
}
void main() {
final logger1 = AppLogger();
final logger2 = AppLogger();
print(identical(logger1, logger2)); // true ✅
}
factoryreturns the same_instanceeach time._internal()keeps external code from creating new instances.
Approach B: Lazy initialization
If the object is expensive to create, initialize it only when used.
class DatabaseService {
static DatabaseService? _instance;
DatabaseService._();
static DatabaseService get instance {
_instance ??= DatabaseService._();
return _instance!;
}
void connect() => print('Connected to DB');
}
void main() {
DatabaseService.instance.connect(); // ✅ Only created when needed
}
This pattern avoids unnecessary memory allocation until the instance is accessed.
Approach C: Using top-level final
For simple global services, you can use a top-level final variable.
final config = Config();
class Config {
final String apiUrl = 'https://api.example.com';
}
No need for constructors or factory methods — Dart ensures one instance per isolate.
Approach D: Singleton with Flutter’s Provider
In Flutter apps, use state management to inject singletons cleanly.
final analytics = AnalyticsService();
void main() {
runApp(
Provider<AnalyticsService>.value(
value: analytics,
child: const MyApp(),
),
);
}
class AnalyticsService {
void logEvent(String event) => print('Event: $event');
}
This makes the singleton accessible throughout the widget tree.
Decision Guide
- For pure Dart services: Use the
factory+ static instance approach. - For lazy loading: Use late initialization (
_instance ??=). - For Flutter apps: Provide the singleton using
ProviderorGetIt. - For simple configs: Use top-level constants or finals.
Recommendation
- Prefer Dart’s built-in singleton patterns over external libraries.
- Use dependency injection (
Provider,Riverpod, orGetIt) for testable and scalable Flutter apps. - Keep singletons stateless when possible to avoid hidden global state.
Leave a Reply