How to create Singleton classes with parameters in Flutter
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:
- Added a nullable string variable
device
to theSettings
class to hold information about a specific device. - Modified the factory constructor
Settings()
to accept adevice
parameter of typeString
. - Inside the factory constructor, the
device
parameter is assigned to thedevice
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:
- 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.
- Logging: A singleton logger class can handle all application log messages, making it easy to track and manage different log levels.
- 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.
- Resource Management: A singleton can efficiently handle the process of creating, using, and releasing resources like database connections or network sockets.
- 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.