What is Dart?
Dart is a modern, object-oriented programming language primarily used for building cross-platform mobile, web, and desktop applications. It was developed by Google and provides a strong type system, which helps catch errors at compile time. Dart supports both Just-In-Time (JIT) and Ahead-Of-Time (AOT) compilation, offering fast development cycles and efficient execution. Its clean syntax makes it easy to read and write code.
What is the singleton pattern?
The Singleton pattern is a design pattern that ensures a class has only one instance and provides a global point of access to it. It involves creating a single object that can only be accessed and used, to put ourselves in a better context, let's take this code:
class MyClass {
int? field;
MyClass();
}
This is a simple Dart class that has a field member called field
, right? now let's say that the purpose of this field is to store a global single value that will be used in more than one place of our Dart/Flutter application, we can't assign a value to that field
instantly when we run our app so we can't make that field a static
field and access it with MyClass.field
. We need a single class to be used and re-used as many times we need in many components of our projects. in other words, we need a singleton class.
How to create a singleton class with Dart?
In order to achieve this pattern and apply it in our project to avoid the previously mentioned cases, there are many ways of getting the job done with Dart, in this article we will discover the most common and easy way for it.
Now, let's take the previous code example:
class MyClass {
int? field;
MyClass();
}
We can create many instances of this class without any restrictions, right? I mean we can do:
final myClassInstanceOne = MyClass()..field = 0;
final myClassInstanceTwo = MyClass()..field = 0;
print(++myClassInstanceOne!.field); // 1
print(++myClassInstanceTwo!.field); // 1
myClassInstanceOne!.field = 20;
print(++myClassInstanceOne!.field); // 20
print(++myClassInstanceTwo!.field); // 1
As you can see above, we had two separated instances of the same class, this means that each one will manage and handles its own members, so by modifying the field
of the myClassInstanceOne
, it is the only one that changes since it is separated from the other myClassInstanceTwo
. In order to have consistent use of the MyClass
. we will need to prevent the construction of it externally as we did above, for this, Dart makes this possible by making the constructor of a class private so nothing can construct it externally, by using an underscore symbol (_) like this:
class MyClass {
int? field;
MyClass._();
}
Now if you tried to create a new instance of MyClass
externally, you will face an error saying:
The class 'MyClass' doesn't have an unnamed constructor.
And this is because it doesn't have the default constructor anymore, all it has is a private one.
But wait, if we can't create an instance of MyClass
, how can we have one?
Well, we didn't say that we can't create an instance, we said that we can't create an instance externally, this means that we can actually make the first and the only instance that is the singleton one that we are going to use every time:
class MyClass {
static MyClass instance = MyClass._();
int? field;
MyClass._();
}
Here, we create that instance of our class internally as a static
object, which represents our singleton, and now in order to access it you will need to use it simply like this:
MyClass.instance;
final singletonField = MyClass.instance.field;
And now across all your application's code, calling the MyClass.instance
will give you access to the singleton instance of that class.
But wait a second, doesn't this have an issue? if you thought of this, then great job, and yes, even this can still work and meets your need for a singleton class, the instance
static field is open for reading which we want exactly, and for modification also which can breaks the singleton pattern since we can re-assign a new value to it:
MyClass.instance = // maybe another singleton or an object that extends MyClass.
To fix this, we need to provide a way to read and access it but prevent setting a new value to it, we can make it also private, and then declare a new Dart getter that will allow the singleton access:
class MyClass {
static MyClass _instance = MyClass._();
static MyClass get instance => _instance;
int? field;
MyClass._();
}
With this, we can still access our singleton as previously using MyClass.instance
but we can't set a new value for it:
MyClass.instance = // Trying too assign anything here will throw an error.
That is it, you just made a singleton class and applied the singleton pattern. you will be able now to use it as you want to ensure that you only have only one instance, and of course, you can apply this to other classes or if you have a use case where you need to have a specific amount of instances only.