Home > database >  What are some null-safe patterns for dealing with database assigned fields such as identifiers?
What are some null-safe patterns for dealing with database assigned fields such as identifiers?

Time:04-14

Being new to null safety and dart, I'm stumbling with the best pattern for dealing with fields that are unset until an object has been saved to a database (identifiers, created timestamps, etc). My searching skills have failed me, but a question like this seems like it would have already been asked (so please point me to it if it has been, and sorry).

The easiest thing to do would be to allow them to be nullable.

class User {
  User(this.id, this.name);

  final String? id;
  final String name;
}

But this means that I need to deal with null checks every time I check that field on the object. That seems overkill considering the only time this happens in practice is when I'm creating a new instance. It seems like the getter method mentioned here might be a reasonable approach, but the use of the ! operator and ignoring the null safety makes me uneasy. I've only recently learned about late, but it seems it would have the same concerns.

So, I split the class up into multiple parts. Effectively, one for the creation of a new instance, and one for everywhere else.

class User extends NewUser {
  User(this.id, String name) : super(name);

  final String? id;
}

class NewUser {
  NewUser(this.name);

  final String name;
}

And this works fine, but it doubles the amount of classes I have, and it means repeating myself a few times when I add new parameters (copyWith, constructors, etc). Adding new models is tedious, as I have to create twice as many.

Is there a better pattern I'm missing? Am I worrying too much about null safety and just need to embrace that sometimes I need to live life on the edge? Or am I already doing the safest thing?

CodePudding user response:

The thing you can do is to have a non-nullable getter which throws if there is no ID yet. Something like:

  String? _id;
  String get id => _id ?? (throw StateError("Not created yet"));
  bool get hasId => _id != null;

Then you don't need to do null checks all the time, but you do risk the code throwing on you instead. Whether that's better or worse is a tradeoff between convenience and safety, you have to decide where you stand.

CodePudding user response:

If you have only one or two properties that you need to keep track of before the object is considered valid, then I'd pass those around as bound callback arguments or store them in a Tuple temporarily. I'd then make everything in User non-nullable and defer constructing the User object until it's ready.

Alternatively, you could make all members non-nullable, initialize a provisional User object with invalid members set to sentinel values (e.g. empty strings), and then replace the User object later. If you do that, I'd also add asserts to check that the object is fully initialized before being used. asserts are a good compromise between having some assurance for expected logical behavior and not penalizing production code.

  • Related