Dart

Create a Singleton in Dart

A Singleton is a design pattern that ensures a class has only one instance while providing a global point of access to that instance.

A Singleton is a design pattern that ensures a class has only one instance while providing a global point of access to that instance. Its primary goal is to control object creation by restricting the number of instances a class can produce to just one.

Advantages

  • Global Access: Provides consistent and easy access to a single instance across the entire application.
  • Resource Sharing: Ideal for managing shared resources such as database connections or configuration files.
  • Simplicity and Efficiency: With only one instance, memory usage is optimized, making the application more efficient.

Disadvantages

  • Challenges in Unit Testing: The global state maintained by a singleton can complicate isolated testing.
  • Hidden Dependencies: Dependencies on a singleton may not be immediately obvious, making the codebase harder to manage and debug.
  • Difficult-to-Trace Errors: An error within the singleton can propagate across the application, making it challenging to locate the root cause.

Use Cases

  • Configuration Files: Loading and storing settings required throughout the application.
  • Caching: Managing a centralized cache for frequently accessed data.
  • Database Connections: Maintaining a single database connection to avoid the overhead of repeatedly creating new ones.
  • SDK Initialization: When integrating third-party libraries or APIs, it is important to initialize the SDK only once. Using the singleton pattern ensures the SDK is set up only once, avoiding conflicts, resource duplication, or inconsistent states. This approach also makes the SDK globally accessible so that all parts of the application use the same configuration and state.

Code

Below is an example of how to implement a singleton in Dart:

class SDKManager {
  // Private named constructor prevents external instantiation.
  SDKManager._sharedInstance();

  // A single, static instance of SDKManager.
  static final SDKManager _instance = SDKManager._sharedInstance();

  // Public getter for the instance.
  static SDKManager get instance => _instance;

  // Factory constructor returns the same instance every time.
  factory SDKManager() {
    return _instance;
  }

  bool _initialized = false;

  // Method to initialize the SDK.
  void initialize() {
    if (!_initialized) {
      print("Initializing SDK...");
      // Insert heavy configuration and resource allocation code here.
      _initialized = true;
    } else {
      print("SDK is already initialized.");
    }
  }
}

void main() {
  // Both sdk1 and sdk2 will refer to the same instance.
  final sdk1 = SDKManager();
  final sdk2 = SDKManager.instance;

  sdk1.initialize(); // Output: Initializing SDK...
  sdk2.initialize(); // Output: SDK is already initialized.
}

Conclusion

The singleton pattern offers the convenience of global access and resource sharing across an application. However, it should be used with caution. Its drawbacks — such as making unit tests harder to isolate, creating hidden dependencies, and potentially causing widespread issues when errors occur — make it unsuitable for every situation. Alternatives like Dependency Injection might be preferable in scenarios where these issues could pose significant challenges.

References