Before reading this article, please read the previous articles so that you can understand Design Patterns in Flutter better. The list of previous articles is provided below.

The Singleton design pattern is one of the most popular design patterns in software development. It's a creational design pattern used when creating objects. If you're unsure what a design pattern is, look at my earlier articles on the design pattern series. In this article, we'll cover.

  1. What is the Singleton Design Pattern?
  2. When to use?
  3. How to implement in Dart?
  4. Pitfalls of Singleton
  5. An example of Singleton in Flutter
  6. Conclusion

Let's get started without any further delay.

What is the Single Design Pattern?

According to the book "Design Patterns, Elements of Reusable Object-Oriented Software," the Singleton design pattern ensures that a class has only one instance and provides a global point of access to it.

Let's put things in simpler terms. When we say "global point of access," we convey that this single instance of the class can be accessed from anywhere in the code without the need to create a new one.

Singleton Design pattern

The Singleton Pattern is a design principle that limits a class to a single instance. It ensures that only one object of that class exists and guarantees that there will always be only one instance of the class in existence at any given time.

This pattern is especially useful when only one object is necessary to coordinate actions across the system. It provides a way to control access to shared resources, such as a database or a file.

When to Use?

It is useful to have singletons when you want to manage just one resource. As examples, here are a few.

How to implement in Dart?

Now, let's try to create the singleton design pattern in Dart. I'm going to break down the code into a few sections so you can understand it better.

Step 1. Create a class

First, we define a class Singleton (you may call it anything, but I'm using Singleton to make things clear). It has a private constructor _internal(). This ensures that no additional Singleton instances are produced from outside the class.

class Singleton {
  static Singleton? _instance;
  Singleton._internal();

Step 2. Create the Singleton class instance

Next, we create a getter instance to see if _instance is null. If so, it generates a new Singleton instance and sets it to _instance. If _instance is not null, it returns the current Singleton instance.

static Singleton get instance {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    return _instance!;
  }

Step 3. Add the functionality to your singleton class

Now, we'll add some functionality to the Singleton class. In this scenario, we've added a counter variable and an increment() method. The increment() method simply increments the counter by one.

int counter = 0;

  void increment() {
    counter++;
  }
}

Step 4. Use the Singleton

Finally, we make use of the Singleton in our main() method. We get the Singleton instance, use its increment() method, and then print the counter value.

void main() {
  Singleton singleton = Singleton.instance;
  singleton.increment();
  print("Counter = ${singleton.counter}");
}

Each time you run this code, the counter will be increased by one. This shows that we are always working with the same Singleton instance, regardless of where we are in the code

Singleton in dart

Pitfalls of Singleton

An example Of Singleton in Flutter

Let's make a user preference class that will save data locally using the shared_preference package, so we'll make a UserPreference Singleton class. This class provides a centralized, efficient approach to managing user preferences in a Flutter application. it ensures that only one instance of the class exists and that user preferences persist across app launches. And we will try to get and set the username.

import 'package:shared_preferences/shared_preferences.dart';

// UserPreferences is a Singleton class that manages user preferences.
class UserPreferences {
  // _instance is a static instance of UserPreferences, initially null.
  static UserPreferences? _instance;

  // Private constructor.
  UserPreferences._internal();

  // Getter for the instance of UserPreferences.
  // If _instance is null, it initializes _instance before returning it.
  static UserPreferences get instance {
    if (_instance == null) {
      _instance = UserPreferences._internal();
    }
    return _instance!;
  }

  // _preferences is an instance of SharedPreferences to store and retrieve data.
  late SharedPreferences _preferences;

  // init is a method to initialize SharedPreferences.
  Future<void> init() async {
    _preferences = await SharedPreferences.getInstance();
  }

  // Getter for the username preference.
  // If the username is not set, it returns an empty string.
  String get username => _preferences.getString('username') ?? '';

  // Setter for the username preference.
  set username(String value) {
    _preferences.setString('username', value);
  }
}

main.dart

// The main function of the application.
void main() async {
  // Ensures that you have an instance of the WidgetsBinding.
  // This is required if you need to access the binary messenger before `runApp()`
  // has been called (for example, during plugin initialization).
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize the UserPreferences singleton instance.
  // This is done before running the app to ensure that the preferences are
  // available to the application as soon as it starts.
  await UserPreferences.instance.init();

  // Run the app. The const keyword here means that the MyApp widget is
  // immutable and its configuration won't change over time.
  runApp(const MyApp());
}

Conclusion

The Singleton design pattern is a vital concept that software developers need to understand. It offers a unique and effective way to manage global access to resources. In this article, we discuss the Singleton pattern, its appropriate use cases, how to implement it in Dart, and its potential drawbacks. We also demonstrate its practical application in Flutter with the UserPreferences class, which efficiently handles user preferences by keeping a single instance of the class. Despite its benefits, it's crucial to consider the potential challenges associated with Singleton, such as global state management, testing difficulties, and potential inflexibility in code modification. If you like my articles, you can connect with me on LinkedIn and say hi.

Resources