Introduction to the Singleton Pattern
The Singleton Pattern is one of the most widely used Creational Design Patterns. Its main goal is to ensure that a class has only one instance and provides a global point of access to it.
When to Use Singleton:
- When exactly one instance of a class is needed (e.g., Logger, Database connection, AppConfig).
- When object creation is expensive and needs to be reused.
- When managing shared resources or global states (e.g., caching, configuration).
Implementing Singleton in Dart
Dart provides a simple and elegant way to implement the Singleton pattern. Below are two common approaches:
Approach 1: Using factory constructor (Most Common)
class Logger {
static final Logger _instance = Logger._internal();
factory Logger() {
return _instance;
}
Logger._internal(); // private named constructor
void log(String message) {
print("[LOG]: $message");
}
}
Explanation:
Logger._internal()is a private constructor used only once to initialize the instance.factory Logger()always returns the same_instance.- This pattern ensures only one instance exists and is reused.
Real-World Example: Logging with Singleton
void main() {
var logger1 = Logger();
var logger2 = Logger();
logger1.log("App is running...");
logger2.log("Processing data...");
print(identical(logger1, logger2)); // true
}
Output:
[LOG]: App is running...
[LOG]: Processing data...
true
Approach 2: Using a static getter (Flutter-style)
class AppConfig {
static final AppConfig _instance = AppConfig._();
AppConfig._(); // private constructor
static AppConfig get instance => _instance;
String apiUrl = "https://api.example.com";
}
Usage:
void main() {
var config = AppConfig.instance;
print(config.apiUrl);
config.apiUrl = "https://dev.api.example.com";
print(AppConfig.instance.apiUrl); // updated value persists
}
Benefits of Singleton
- Guarantees a single instance across the app.
- Centralized control of shared resources.
- Reduces memory and initialization overhead.
Drawbacks
- Can introduce global state, which may hurt testability.
- Violates Single Responsibility Principle if misused.
- Not ideal for apps requiring multiple isolates unless explicitly managed.
Tips & Best Practices
- Use Singleton only when it makes sense (logging, configuration, etc.).
- Be careful when mixing Singletons with state management libraries like Provider, Riverpod, or GetX.
- Prefer immutability for Singleton state whenever possible.
Summary Table
| Attribute | Singleton Pattern |
|---|---|
| Purpose | Ensure a class has only one instance |
| Easy in Dart? | Yes (factory + private constructor) |
| Common Use Cases | Logger, Config, DatabaseService |
| Thread-safe? | Yes in Dart (single-threaded by default) |



