What is single ten design pattern ?
Answer
Overview
The Singleton design pattern ensures that a class has only one instance throughout the entire application lifecycle, and provides a global access point to that instance.
Think of it like a country's President — there can only be one at a time, and everyone refers to the same person.
When to Use Singleton
- Database connections (only one connection pool)
- App configuration / settings
- Logger service
- Network client (e.g., single instance)text
Dio - Dependency injection containers
Basic Singleton in Dart
dartclass AppConfig { // Step 1: Static private instance static AppConfig? _instance; // Step 2: Private constructor AppConfig._privateConstructor(); // Step 3: Public factory method to return the same instance factory AppConfig() { _instance ??= AppConfig._privateConstructor(); return _instance!; } // Your actual fields String apiBaseUrl = 'https://api.example.com String apiKey = 'YOUR_API_KEY'; } // Usage void main() { final config1 = AppConfig(); final config2 = AppConfig(); print(identical(config1, config2)); // true — same instance! }
Simplified Singleton (Using Static Instance)
dartclass Logger { // Create the single instance immediately static final Logger instance = Logger._internal(); Logger._internal(); void log(String message) { print('[LOG] ${DateTime.now()} — $message'); } } // Usage Logger.instance.log('App started'); Logger.instance.log('User logged in');
Singleton for Network Client (Practical Example)
dartimport 'package:dio/dio.dart'; class ApiClient { static final ApiClient _instance = ApiClient._internal(); late final Dio dio; factory ApiClient() => _instance; ApiClient._internal() { dio = Dio(BaseOptions( baseUrl: 'https://api.example.com connectTimeout: Duration(seconds: 10), receiveTimeout: Duration(seconds: 10), )); // Add interceptors once dio.interceptors.add(LogInterceptor(responseBody: true)); } Future<dynamic> get(String path) async { final response = await dio.get(path); return response.data; } } // Usage anywhere in app final data = await ApiClient().get('/users');
Thread-Safe Singleton (Dart Isolates)
Dart is single-threaded in its main isolate, so the basic singleton above is already thread-safe for most Flutter use cases. However, if you're using multiple isolates:
dartclass DatabaseHelper { static final DatabaseHelper _instance = DatabaseHelper._internal(); factory DatabaseHelper() => _instance; DatabaseHelper._internal(); // Lazy initialization of heavy resource Database? _db; Future<Database> get database async { _db ??= await _initDatabase(); return _db!; } Future<Database> _initDatabase() async { return await openDatabase('app.db', version: 1); } }
Singleton in GetX
dart// Register as singleton Get.put(ApiService()); // Eager — created immediately Get.lazyPut(() => ApiService()); // Lazy — created on first access // Access anywhere final api = Get.find<ApiService>();
Singleton in get_it (Dependency Injection)
dartfinal getIt = GetIt.instance; // Register getIt.registerSingleton<ApiService>(ApiService()); // Access anywhere final service = getIt<ApiService>();
Pros and Cons
| Pros | Cons |
|---|---|
| ✅ Single shared instance — saves memory | ❌ Hard to unit test (global state) |
| ✅ Global access point | ❌ Can hide dependencies (bad for DI) |
| ✅ Lazy initialization possible | ❌ Violates Single Responsibility if overused |
| ✅ Useful for shared resources | ❌ Can cause tight coupling |
Common Mistakes
dart// ❌ Wrong — not a singleton, creates new instance each time class NotASingleton { static NotASingleton create() => NotASingleton(); } // ✅ Correct — always returns same instance class Singleton { static final Singleton _instance = Singleton._(); factory Singleton() => _instance; Singleton._(); }
Best Practice: In Flutter, prefer using
ortextGetItfor managing singletons rather than rolling your own — they provide better lifecycle management and testability.textRiverpod