Singleton Pattern
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 90 of 110.
The Singleton pattern ensures that a class has only one instance throughout your entire application, and provides a global point of access to it. This is useful when exactly one object is needed to coordinate actions across the system - like a configuration manager, a database connection, or a logging service.
In Dart, you can implement a Singleton using a factory constructor combined with a private static instance:
class AppConfig {
static final AppConfig _instance = AppConfig._internal();
String appName = 'MyApp';
String version = '1.0.0';
// Private named constructor
AppConfig._internal();
// Factory constructor returns the same instance
factory AppConfig() {
return _instance;
}
}
void main() {
var config1 = AppConfig();
var config2 = AppConfig();
config1.appName = 'UpdatedApp';
print(config2.appName); // UpdatedApp
print(identical(config1, config2)); // true
}The private constructor _internal() prevents external instantiation. The static _instance field holds the single instance, created once when the class loads. Every call to AppConfig() returns this same instance, so changes made through one reference are visible everywhere.
Use Singletons sparingly - they introduce global state, which can make testing harder. They're best suited for truly shared resources that need consistent state across your application.
Challenge
EasyLet's build a game settings manager using the Singleton pattern! You'll create a system that ensures only one instance of game settings exists throughout the application, so all parts of your game share the same configuration.
You'll organize your code into two files:
game_settings.dart: Create aGameSettingsclass that implements the Singleton pattern. Your settings manager should have:- A private static final instance that holds the single
GameSettingsobject - A private named constructor to prevent external instantiation
- A factory constructor that always returns the same instance
- Three settings fields:
difficulty(String, default: "Normal"),soundEnabled(bool, default: true), andplayerName(String, default: "Player1") - A method
displaySettings()that prints the current settings in this format:Settings: [playerName] | Difficulty: [difficulty] | Sound: [on/off](print "on" if soundEnabled is true, "off" if false)
- A private static final instance that holds the single
main.dart: Import your game settings and demonstrate that the Singleton works correctly:- Get the settings instance and call it
settings1 - Call
displaySettings()to show the default values - Modify the settings: change
playerNameto "Hero",difficultyto "Hard", andsoundEnabledto false - Get another reference to settings and call it
settings2 - Call
displaySettings()onsettings2to prove it reflects the changes made throughsettings1 - Print whether
settings1andsettings2are identical using:Same instance: [true/false]
- Get the settings instance and call it
This demonstrates the core benefit of Singletons - changes made anywhere in your application are immediately visible everywhere else, because there's only one instance!
Expected output:
Settings: Player1 | Difficulty: Normal | Sound: on
Settings: Hero | Difficulty: Hard | Sound: off
Same instance: trueCheat sheet
The Singleton pattern ensures that a class has only one instance throughout your entire application, and provides a global point of access to it.
In Dart, implement a Singleton using a factory constructor combined with a private static instance:
class AppConfig {
static final AppConfig _instance = AppConfig._internal();
String appName = 'MyApp';
String version = '1.0.0';
// Private named constructor
AppConfig._internal();
// Factory constructor returns the same instance
factory AppConfig() {
return _instance;
}
}Key components:
static final _instance: Holds the single instance, created once when the class loads_internal(): Private constructor prevents external instantiationfactory AppConfig(): Returns the same instance every time
Usage example:
var config1 = AppConfig();
var config2 = AppConfig();
config1.appName = 'UpdatedApp';
print(config2.appName); // UpdatedApp
print(identical(config1, config2)); // trueChanges made through one reference are visible everywhere because all references point to the same instance.
Try it yourself
import 'game_settings.dart';
void main() {
// TODO: Get the settings instance and call it settings1
// TODO: Call displaySettings() to show the default values
// TODO: Modify the settings:
// - Change playerName to "Hero"
// - Change difficulty to "Hard"
// - Change soundEnabled to false
// TODO: Get another reference to settings and call it settings2
// TODO: Call displaySettings() on settings2 to prove it reflects the changes
// TODO: Print whether settings1 and settings2 are identical
// Format: Same instance: [true/false]
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor