We start from where we already have an app with interesting features; the last thing was installing, configuring, and using fonts in Flutter. Now, we're going to learn a very important topic: good practices in our app, design breaks.
Simply put, a singleton ensures that a class has only one instance in all applications and provides a global access point.
An instance or object of a Class is a concrete and specific representation of a Class
The Singleton pattern is one of many design patterns that guarantees that a class has only one instance; and it is basically that; for this, object-oriented programming is used. In Flutter, the Singleton pattern is often used in combination with the Material layout to provide global access to certain Material widgets used in the app.
For example, a singleton DatabaseHelper can be used to provide global access to an SQLite database; this is an implementation that I commonly do in the Flutter course and in my apps; These types of adaptations are common in Flutter and facilitate the process of creating organized, modular and scalable applications.
In general, the implementation of a Singleton pattern in Flutter follows the same principles as in any other programming language, ensuring that only one instance of a given class is created, and providing a global access point for that instance.
What is the Singleton pattern in Flutter and why is it used?
The Singleton is a creational pattern that guarantees a class has only one instance throughout the application's lifetime, and that this instance is accessible from anywhere.
The logic behind having a single instance
Consider these typical situations:
- Save the dark mode state.
- Remember the username after login.
- Access a global HTTP client.
- Maintain a single connection to SQLite.
In my own apps, I've often tried to handle simple configurations using `shared_preferences`, and I quickly discovered that replicating logic across different screens rapidly made the code messy. A Singleton solves that problem at its root.
When to use it?
This pattern is used when we need only one instance of something in our application. For example, in the case of a user login in our application, we need this information in several parts, but it would be too expensive in terms of processing to have the user information for each flow. In this case we can create a user singleton and it would be the same instance for all streams.
Benefits
- Controlled access to a single instance of data. That is, you can have strict control over how and when clients access.
- The instance is created only when it is to be used.
- Accessible from anywhere, singleton is an improvement over global variables.
How do we guarantee that a Class has only one instance?
One solution is to make the Class itself responsible for its only instance. The Class can ensure that no other instance can be created and must provide a way to access the instance.
Singleton in Flutter
One way to create a singleton in Dart is to use factory constructors. As an example, let's create a user singleton that will hold the username. To do this, we first create a regular Class called UserSingleton. We make it a singleton by creating a private variable _instance of type UserSingleton. We then create it using a UserSingleton() factory that returns the instance.
Finally, we create the UserSingleton._internal() constructor which is called exactly once, and since it is private it can only be called in this Class and we also prevent it from being instantiated outside of here. This constructor can be used to initialize the logic.
In the example we are going to use, we also have a method to update the username.
Finally, the UserSingleton is as follows:
class UserSingleton {
static final UserSingleton _instance = UserSingleton._internal();
String userName = 'Alvaro';
// using a factory is important
// because it promises to return _an_ object of this type
// but it doesn't promise to make a new one.
factory UserSingleton() {
return _instance;
}
// This named constructor is the "real" constructor
// It'll be called exactly once, by the static property assignment above
// it's also private, so it can only be called in this class
UserSingleton._internal() {
// initialization logic
}
// rest of class as normal, for example:
void updateUserName(String name) {
userName = name;
}
}Here you can see another Singleton implementation for a database helper:
class DatabaseHelper {
static DatabaseHelper _instance;
factory DatabaseHelper() => _instance ??= DatabaseHelper._();
DatabaseHelper._();
// aquí puedes agregar métodos y propiedades relacionados con la base de datos
}In this example, the DatabaseHelper class has a static class property _instance that contains the single instance of the class. A factory constructor is used to create a new instance of the class if it doesn't already exist, or to return the existing instance if it has already been created.
Remember that…
Using factory constructors in Dart is used when you don't need to create an instance of the class itself or where you have an instance of a subclass or the same class,
By using the factory constructor, you can control how the class is instantiated, and can often optimize and reuse the same instance for subsequent calls. This can be useful in situations where creating a new instance of a class can be costly in terms of time and resources.
The current DatabaseHelper._() constructor is private, which means it cannot be called from outside the class, forcing an instance to be created via the /factory constructor.
To use the DatabaseHelper in your application, simply call the factory constructor, as shown below:
DatabaseHelper db = DatabaseHelper();How to Implement a Singleton in Dart (Recommended Ways)
Dart makes this pattern much easier thanks to factory constructors and private constructors.
Singleton with Factory Constructor
This is one of the most common and clearest ways:
class Singleton {
static final Singleton _instance = Singleton._internal();
factory Singleton() {
return _instance;
}
Singleton._internal();
}Advantages:
- Always returns the same instance.
- Full control over when and how it's initialized.
On one occasion, I used this very pattern for a DatabaseHelper and it worked perfectly, even in apps with heavy navigation.
Singleton with Static Instance
An even more explicit pattern:
class Config {
Config._privateConstructor();
static final Config instance = Config._privateConstructor();
String apiUrl = 'https://api.example.com';
}It is used like this:
final config = Config.instance;Lazy vs Eager Initialization
Eager (immediate instance): useful if the class is lightweight.
Lazy (only created when needed): ideal if initialization is costly.
Lazy example:
class LazySingleton {
LazySingleton._();
static LazySingleton? _instance;
static LazySingleton get instance {
_instance ??= LazySingleton._();
return _instance!;
}
}Practical Examples of Singleton in Flutter
These are real cases I often see — and use — in production apps.
Case 1: Simple Global State
When I was testing design patterns to teach in my courses, I often demonstrated how a Singleton can manage something as simple as global text that changes from any screen.
class AppState {
static final AppState _instance = AppState._internal();
String text = "Hi";
factory AppState() => _instance;
AppState._internal();
void updateText(String newText) => text = newText;
}Case 2: SharedPreferences as a Singleton
This pattern is essential when the state must persist.
class UserPreferences {
static final UserPreferences _instance = UserPreferences._internal();
factory UserPreferences() => _instance;
UserPreferences._internal();
SharedPreferences? _prefs;
Future<void> initPrefs() async {
_prefs = await SharedPreferences.getInstance();
}
String get defaultRoute => _prefs?.getString('defaultRoute') ?? '/login';
set defaultRoute(String value) => _prefs?.setString('defaultRoute', value);
}I used to replicate calls to SharedPreferences across various screens; making it a Singleton completely cleaned up my architecture.
Case 3: DatabaseHelper for SQLite
This is one of the cases I use most often in my apps:
class DatabaseHelper {
static DatabaseHelper? _instance;
factory DatabaseHelper() => _instance ??= DatabaseHelper._();
DatabaseHelper._();
}A single instance manages the entire database efficiently.
When to use (and when NOT to use) a Singleton
✔ When to use it
- You need a single shared instance.
- Class creation is costly.
- You must manage global configurations.
- You want to avoid duplicate states between screens.
In my own work, having repeated user instances in every flow cost me performance until I adopted a Singleton.
✖ When NOT to use it
- If you can better inject dependencies with Provider, Riverpod, or GetIt.
- If you need multiple instances for testing.
- If the state isn't truly global.
- Overuse of the pattern can couple modules too much.
Best Practices for Using Singletons in Flutter
Testing, Modularity, and Maintenance
- Keep the logic within the Singleton as atomic as possible.
- Don't overuse global access for everything.
- Avoid the Singleton becoming a "God Class".
- How to avoid tightly coupled code
Use interfaces if you need to swap instances.
Limit access by writing specific methods, not exposing everything.
Complement with providers when there's more than one important state.
Frequently Asked Questions about Singleton in Flutter
- Is it bad to use Singletons?
No, the bad thing is overusing them. Use them where they truly make sense. - What's the difference between factory and static instance?
The factory allows additional logic to decide which instance to return. Static instance is more direct. - Is it thread-safe in Flutter?
Yes, because Dart guarantees unique initialization in isolates. - Is it better to use Provider or a Singleton?
It depends: Provider manages reactive state; Singleton, non-reactive global state.
Conclusion
The Singleton pattern in Flutter is simple yet powerful. It saved me on multiple occasions, especially when I was looking to keep my architecture clean as my apps grew. It's not magic, but when applied well, it saves you time, errors, and clutter.
With clear examples, effective variations, and best practices, you now have a complete guide to decide when and how to use it in your projects.
Now, learn how to use the Observer pattern in Flutter.
I agree to receive announcements of interest about this Blog.
Simply put, a singleton ensures that a class has only one instance in all applications and provides a global access point. We will see how to use it in Flutter.