Singleton Class in Flutter with NullSafety
Asked Answered
P

4

10

I have this class which takes some parameters by using the factory constructor, if instance is null, a new object will be created; if it's not null, the value of instance will be returned so we always receive the same object all the time (Singleton). This is how I used the singleton pattern before enabling null-safety features of dart.

class GuestUser extends User {
  static GeustUser _instance;


  factory GuestUser(
      {required String firstName,
      required String lastName,
      required String email,
      required Address address,
      required PaymentInfo paymentInfo}) {
    if (_instance == null) {
      _instance =
          GuestUser._(firstName, lastName, email, address, paymentInfo);
    }
    return _instance;
  }

Now with null-safety enabled, I get this error:

The non-nullable variable '_instance' must be initialized.
Try adding an initializer expression.

Also if (_instance == null) is not needed anymore.

If I define the _instance like this: static late final GuestUser _instance; then I cannot use the if (_instance == null) to only create the _instance when needed. so I have to remove the if statement and create a new instance every time the factory constructor is called.

How can I solve this issue and create a singleton class with null-safety enabled?

I have this solution in mind to keep track of the instance with a boolean variable:

static late final GeustUser _instance;
static bool _isInstanceCreated = false;

  factory GuestUser(
      {required String firstName,
      required String lastName,
      required String email,
      required Address address,
      required PaymentInfo paymentInfo}) {
    if (_isInstanceCreated == false) {
      _instance =
          GuestUser._(firstName, lastName, email, address, paymentInfo);
    }
    _isInsanceCreated = true;
    return _instance;
  }

But I want to know whether there is a way to do this without defining new variable and by using the features of the null-safety

Paoting answered 6/3, 2021 at 20:12 Comment(0)
A
5

Your singleton is a little strange since your are taking arguments which will only be used the first time it is called. It is not a great pattern since it can give some surprises if e.g. your want to use it for two different guest users.

In your example it makes sense to just use GuestUser? as the type for _instance. Non-nullable by default in Dart should not be seen as the use of null is bad and you should definitely use null where it makes sense (especially if we can prevent the introduction of bool variables to indicate if a variable has been set). In your example, _instance is null until it is initialized the first time.

Example where we use the ??= operator. The operator will check if _instance is null and in case of null, it will assign the variable to the object created by calling the constructor GuestUser._. It will hereafter return the value of _instance. If _instance does already have a value (not null), this value will just be returned without calling the GuestUser._ constructor.

class GuestUser extends User {
  static GuestUser? _instance;

  factory GuestUser(
          {required String firstName,
          required String lastName,
          required String email,
          required Address address,
          required PaymentInfo paymentInfo}) =>
      _instance ??=
          GuestUser._(firstName, lastName, email, address, paymentInfo);
}

If you had a more traditional singleton, you could have made a static final variable where the instance is created in the definition. But this does not allow for parameters.

Adactylous answered 6/3, 2021 at 20:30 Comment(0)
C
4

You can use the same traditional way to create a singleton. You only have to put the null-safety operator in the right place.

class MySingleton {
  // make this nullable by adding '?'
  static MySingleton? _instance;

  MySingleton._() {
    // initialization and stuff
  }

  factory MySingleton() {
    if (_instance == null) {
      _instance = new MySingleton._();
    }
    // since you are sure you will return non-null value, add '!' operator
    return _instance!;
  }
}
Cele answered 9/6, 2021 at 9:52 Comment(2)
and how to call. please tell me in the commentConnelley
@Connelley You can simply do foo = MySingleton() to create the instance of the class.Cele
S
2

As @julemand101 mentioned, your singleton is actually strange, because generally you'd do something like:

class Foo {
  static final instance = Foo._();
  Foo._();
}

However, you can't instantiate it with the parameters. For that you can do this:

class Bar {
  static Bar? instance;
  Bar._(int i);

  factory Bar.fromInt(int i) {
    var bar = Bar._(i);
    instance = bar;
    return bar;
  }

  void display() => print('Bar instance');
}

void main() {
  final nullableBar = Bar.instance;
  final nonNullableBar = Bar.fromInt(0);

  nullableBar!.display(); // Runtime error as Bar.instance is used before Bar.fromMap(0)
  nonNullableBar.display(); // No error
}
Sugarcoat answered 18/5, 2021 at 13:54 Comment(2)
This is good thanks but It's better to pass a map to the factory as the name is fromMap but you're passing an int. it of course works but using a map is better for this example.Paoting
@Paoting Initially I was going to use Map (which is the general pattern) but to make the code short I used int. Oops I just noticed that I'm using fromMap word there. I should actually change the code. Haha. Thanks for noticing!Sugarcoat
S
1

What you are getting as it is part of Lazy Initialization, with this approach we create an instance at runtime, not at the time of initialization.

Lastly, your problem will be solved using the null aware operator, and better use factory constructor for initialization of an object.

class GuestUser  {
  static GuestUser? _instance;

  GuestUser._( 
       String firstName,
       String lastName,
       String email,
       String address,
       String paymentInfo);

   factory GuestUser(
      {required String firstName,
      required String lastName,
      required String email,
      required String address,
      required String paymentInfo}) {
    _instance ??=
          GuestUser._(firstName, lastName, email, address, paymentInfo);
     return _instance!;
   }
  ///.. Rest code
}

Create object like

  GuestUser user = GuestUser(firstName: "jitesh", lastName: "mohite", email: "[email protected]", address: "Universe", paymentInfo: "As you like");
Scanlan answered 29/9, 2021 at 10:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.