[Solved] How to use a factory constructor with null safety

Question

Asked by Carleton Y on November 16, 2021 (source).

I'm trying to figure out if my updated code is the correct way to use a factory constructor with null safety. I reviewed stackoverflow and the Dart.dev language tour to try to better understand factory constructors. I have struggled to apply the concepts outlined to my code. I'm new to Flutter, Dart and coding. This is my first attempt at using a factory constructor so the primary issue is my lack of understanding and not any issues with the answers on stackoverflow or elsewhere.

After reading a lot I settled on the approach in the code below marked as Updated. The errors are now all gone and my app is behaving as I want but my fear is throwing an error instead of returning null may not be a sound approach. My approach just looks wrong to my beginner eyes. My goal is for my code to work and to also understand why I am using whatever approach I am using so that I can apply that knowledge to future situations. I can provide any additional code that may be needed to comment. Thanks in advance for the help.

Original Code that throws an error

class Job {
  Job({required this.name, required this.ratePerHour});

  factory Job.fromMap(Map<String, dynamic>? data) {
    if (data == null) {
      return null;
    }
    final String name = data['name'];
    final int ratePerHour = data['ratePerHour'];
    return Job(name: name, ratePerHour: ratePerHour);
  }

  final String name;
  final int ratePerHour;

  Map<String, dynamic> toMap() {
    return {
      'name': name,
      'ratePerHour': ratePerHour,
    };
  }
}

Updated code that works

class Job {
  Job({required this.name, required this.ratePerHour});

  factory Job.fromMap(Map<String, dynamic>? data) {
    if (data != null) {
      final String name = data['name'];
      final int ratePerHour = data['ratePerHour'];
      return Job(name: name, ratePerHour: ratePerHour);
    } else {
      throw ArgumentError('Data is null');
    }
  }

  final String name;
  final int ratePerHour;

  Map<String, dynamic> toMap() {
    return {
      'name': name,
      'ratePerHour': ratePerHour,
    };
  }
}

Answer

Question answered by venir (source).

You've pointed out several true and good facts, and I feel like that you're on the right way to implement this.

I also feel like there's no straight "right" answer to this question; I think this also connects to concepts as clean code and clean architecture, which are broader than Dart and Flutter themselves

You can either:

  1. Throw and let the caller (upper layer) handle that problem;
  2. Print some logs and return a zero-value to the caller (in your case, an "empty" object).

Case 1 is desirable if you don't want to handle cases like that one.

Case 2 is desirable if you can afford to return something weird like a Job("job name",0) and still be good.

It really depends on what you're building. By looking at your context, I'd probably go with option 1 and try/catch that in a middle layer (maybe you want to show your user "An error occured" whenever data is null)?

CONSTRUCTOR DART FLUTTER
SHARE: