DEV Community

Cover image for Dart -  applying zero objects and isEmpty pattern to everything
Anton Malofeev
Anton Malofeev

Posted on • Edited on

Dart -  applying zero objects and isEmpty pattern to everything

It's more of a technical note, rather than an article, so I will try to express the idea more in a condensed and clear manner.

Before getting started, if your way to see how it works, I've recently uploaded small package - see it here.

Coming from the JS/TS world, when I first used Dart, the most fascinating thing among others was to use the isEmpty or isNotEmpty functions, and for strings, lists, maps, etc. It was amazingly simple, just not to write every time .length == 0.

Also, it was very useful to use empty/zero values like Duration.zero, Offset.zero, and many others that were a nice addition to this pattern.

Over time, I developed a habit to use the same principle for different cases and came to think, what if we use zero values for most cases, eliminating the need for null for most cases (not all, but nonetheless)? I searched and found that a similar pattern is used in Go, so I've started thinking:

Problem

imagine situation:

String? value;

// do something

value = "new value”;
Enter fullscreen mode Exit fullscreen mode

What checks should we make to ensure the value exists to assign some value to a new variable?

final String newValue;
if(value != null && value.isEmpty){
  // do something
  newValue = defaultValue;
}  else {
  newValue = value;
}
Enter fullscreen mode Exit fullscreen mode

What is the difference between null and isEmpty check in this situation?
What if we write

String value = “”;
final String newValue;

if(value.isEmpty){
  // do something
  newValue = defaultValue;
} else {
  newValue = value;
}

// use newValue
Enter fullscreen mode Exit fullscreen mode

Nice but still verbose.
Would that be nice to use it inline? Let's try to rewrite it with a new extension:

extension StringX on String {
  String whenEmptyUse(String value) => isEmpty ? value : this;
}

final newValue = value.whenEmptyUse("defaultValue");
Enter fullscreen mode Exit fullscreen mode

What if we try to apply it to lists, maps, values, and numbers?

extension ListX on List<T> {
  List<T> whenEmptyUse(List<T> values) => isEmpty ? values : this;
}

final newValues = values.whenEmptyUse(['defaultValue']);

extension DoubleX on double {
  bool isZero => this == 0;
  double whenZeroUse(final double value) => isZero ? value : this;
}

final opacity = 0;

final newOpacity = opacity.whenZeroUse(0.5);
Enter fullscreen mode Exit fullscreen mode

This pattern is extremely useful with zero/empty values for objects too. For example:

class Person {
  const Person({this.name=''});
  final String name;

  static const empty = Person();

  bool get isEmpty => name.isEmpty;
  bool get isNotEmpty => name.isNotEmpty;
  // or use different names for to read nicer
  bool get isInitialized => name.isNotEmpty;
  bool get isNotInitialized => name.isEmpty;
}

extension PersonX on Person {
 Person whenEmptyUse(Person value) => isEmpty ? value : this;
}

Enter fullscreen mode Exit fullscreen mode

Then we could use it everywhere the same simple way as checking a string:

var person = Person.empty;

// do something dangerous

final newPerson = person.whenEmptyUse(Person(name:'Frodo'));
Enter fullscreen mode Exit fullscreen mode

We could do same for IDs, Lists with types, etc. etc..
This way we use simpler API, always know for sure that we have some value and we know how to identify default (empty, zero) value.

Hopefully it helps:) 

Please don't forget to share your thoughts in comments:) it really helps the algorithms give this article to others to read, and it would be great support from you:)

Have a nice day!

Top comments (2)

Collapse
 
randalschwartz profile image
Randal L. Schwartz • Edited

You can also use the Option meta-type from functional programming (my favorite for that is package:fpdart) to indicate that something is there, or not there.

Collapse
 
arenukvern profile image
Anton Malofeev • Edited

Thanks! fpdart is brilliant library, and I think can be used along with the zero/empty objects or completely replace them at some cases.

For example I think in games, it is easier to create and reuse one instance, instead of several since it will be rendered anyway.

For some ui elements, like lists, it is nice to have empty models, or mock models to use them as foundation for skeleton graphics.

Personally, I use options-like style usually in places, where we expect only two cases: error or value, for example response from put request, or from mutation.