How to create Singleton classes with parameters in Flutter

Flutter Jul 19, 2023

Singleton classes are valuable when you want to ensure that only one instance of a class exists and can be accessed globally. In this post, we will explore the implementation of singleton classes in Flutter. We will start with a basic example, and right after explore singleton classes with parameters. We will also cover use cases where singletons can be beneficial.

Singleton

In Flutter, creating a Singleton class does not require a lot of code. Below is a code snippet that shows an implementation of a singleton pattern in Dart using the Settings class.

class Settings {
  static final Settings _settings = Settings._internal();

  Settings._internal();

  factory Settings() {
    return _settings;
  }
}

In this code snippet, we created a class called Settings. Inside the Settings class, is a private static variable named _settings, which holds an instance of the Settings class. This variable is marked as final to ensure it can only be assigned once.

To create the singleton instance, the _settings variable is initialized with a private function called _internal(). This function is private, meaning it can only be used within the Settings class and not accessed from outside.

To get the singleton instance, a factory constructor named Settings() is defined. This constructor is responsible for returning the single instance stored in the _settings variable. It ensures that only one instance of Settings is created and returned, no matter how many times the constructor is called.

By using this approach, the singleton instance remains the same throughout the application, and other parts of the code can access it by calling the Settings() constructor. This guarantees that there is always just one Settings instance available.

Testing

We can easily test if our constructor indeed creates singleton instances, see the following code:

void main() {
  Settings firstSettings = Settings();
  Settings secondSettings = Settings();

  print(firstSettings == secondSettings);
}

In this code snippet, inside the main() function, we create two instances of the Settings class by invoking the Settings() constructor twice and assigning them to the variables firstSettings and secondSettings.

To compare the equality of these two instances, we use the == operator, which checks if two objects are referencing the same instance.

When we run this code it will return true.

Singleton with parameters

It is also possible to implement a Singleton with a constructor that takes parameters, see the code below:

class Settings {
  String? device;
  
  static final Settings _settings = Settings._internal();

  Settings._internal();

  factory Settings(String device) {
    _settings.device = device;

    return _settings;
  }
}

In the original code snippet, we defined a class named Settings. With a static variable_settings to hold the singleton instance of the Settings class. The _settings variable is initialized using a private named constructor _internal() and a factory constructor Settings() is provided to retrieve the singleton instance.

The updated code snippet includes the following changes:

  1. Added a nullable string variable device to the Settings class to hold information about a specific device.
  2. Modified the factory constructor Settings() to accept a device parameter of type String.
  3. Inside the factory constructor, the device parameter is assigned to the device variable of the _settings instance, allowing the device information to be set.

Testing

Now that our constructor accepts parameters we have to make changes inside our main function, see the following code:

void main() {
  Settings firstSettings = Settings('Android');
  Settings secondSettings = Settings('Apple');

  print(firstSettings == secondSettings);
  print(firstSettings.device);
  print(secondSettings.device);
}

In this updated code snippet, we still create two instances of the Settings class by invoking the Settings() constructor twice and assigning them to the variables firstSettings and secondSettings. However we now also provide the parameter inside the Settings() constructor.

Furthermore, we display the device property of both instances using print(firstSettings.device) and print(secondSettings.device). As a result, you will see the following output:

true
Apple
Apple

Our instances are still Singletons. However, when we check the results, we notice that the device value we set during instance creation is replacing the original one. Because both print statements are showing the last set parameter Apple.

Overwriting prevention

To prevent overwriting of the parameter, we can update the code with the following changes:

class Settings {
  String? device;
  
  static final Settings _settings = Settings._internal();

  Settings._internal();

  factory Settings(String device) {
    if (_settings.device == null) _settings.device = device;

    return _settings;
  }
}

In the updated code snippet, we added an if statement that checks if the device variable is still null. Only if this variable is still null we assign a new value.

Now when we run our main function again we will get the following results:

true
Android
Android

As you can see we get Android twice which is the first parameter that we set.

Use cases

Singleton classes are helpful in various situations where you need only one copy of a class that can be used from anywhere in your application. Here are some scenarios where singletons can be useful:

  1. Configuration or Settings: If your application has global settings that different parts of the application use and modify, a singleton can manage those settings, like themes, logging options, or API addresses.
  2. Logging: A singleton logger class can handle all application log messages, making it easy to track and manage different log levels.
  3. Utility Classes: Singletons can be used for utility classes with common functions or helper methods, so you always have access to them anywhere in the application.
  4. Resource Management: A singleton can efficiently handle the process of creating, using, and releasing resources like database connections or network sockets.
  5. Caches: Singletons are great for managing caches like image or data storage, making sure data is stored and retrieved consistently without duplication.

Conclusion

In this post, we learned about singleton classes in Flutter. We learned how to create basic singleton classes, use parameters, and prevent overwriting of parameters. Singleton classes are useful because they make sure there is only one instance in the application, which makes it easy to access globally. By understanding and using singleton classes effectively, you can improve your Flutter development skills and create more efficient applications.

Tags