Dart null / false / empty checking: How to write this shorter?
This is my code for true on everything but empty string, null and false:
if (routeinfo["no_route"] == "" || routeinfo["no_route"] == null || routeinfo["no_route"] == false) {
// do sth ...
}
This is my code for true on everything but empty string, null, false or zero:
if (routeinfo["no_route"] == "" || routeinfo["no_route"] == null || routeinfo["no_route"] == false || routeinfo["no_route"] == 0) {
// do sth...
}
How can I write this shorter in Dart? Or is it not possible?
Solution 1:
If your requirement was simply empty or null (like mine when I saw this title in a search result), you can use Dart's safe navigation operator to make it a bit more terse:
if (routeinfo["no_route"]?.isEmpty ?? true) {
//
}
Where
-
isEmpty
checks for an empty String, but if routeinfo isnull
you can't call isEmpty on null, so we check for null with -
?.
safe navigation operator which will only call isEmpty when the object is not null and produce null otherwise. So we just need to check for null with -
??
null coalescing operator
If your map is a nullable type then you have to safely navigate that:
if (routeinfo?["no_route"]?.isEmpty ?? true) {
//
}
Solution 2:
You could do
if (["", null, false, 0].contains(routeinfo["no_route"])) {
// do sth
}
Solution 3:
Late 2020 Update
Summary
- This answer holds true, except for
isNull
andisNotNull
. They no longer provide type promotion whenNull Safety
is introduced in dart/flutter in the future. - Other helpers like
isNullOrEmpty
do not provide type promotion, as they are in a different (sub-)scope compared to callsite. - My personal opinion, is that you can drop
isNull
andisNotNull
but keep other helpers as you shouldn't expect them to do type promotion for you.
Explanation
- This is because
Null Safety
was finally introduced in Dart/Flutter. But as of October 2020, this feature is still not available for stable releases on dart/flutter. Check out the quick guide Null Safety or the thorough Understanding Null Safety. - Null Safety in Dart/Flutter should be similar to Swift (Optional) and Kotlin (Nullable Types, Non-Nullable Types).
Demonstration
Here's a demonstration why encapsulation/helper-getter of isNull
(== null
) and isNotNull
(!= null
) is a very big problem:
// Promotion works
int definitelyInt(int? aNullableInt) {
if (aNullableInt == null) { // Promote variable `aNullableInt` of Nullable type `int?` to Non-Nullable type `int`
return 0;
}
return aNullableInt; // Can't be null! This variable is promoted to non-nullable type `int`
}
When "Null Safety" is shipped in the dart release you are using, the type promotion in above code works! HOWEVER:
// Promotion does NOT work!!!
int definitelyInt(int? aNullableInt) {
if (aNullableInt.isNull) { // does NOT promote variable `aNullableInt` of Nullable type `int?`
return 0;
}
return aNullableInt; // This variable is still of type `int?`!!!
}
The above doesn't work, because the null check == null
and != null
are encapsulated in a sub-scope (different stack frame) and not inferred to have this "promotion" effect within definitelyInt
scope.
Early 2020 Update
Here's a modified version using getters instead of instance/class methods and covering whitespaces, isNull, and isNotNull.
Second Update
It also accounts for empty lists and maps now!
Third Update
Added private getters for safety and modularity/maintainability.
Fourth Update
Updated the answer to fix a particular problem with Map
, it's not correctly being handled when empty! Because Map
is not an Iterable
. Solved this issue by introducing _isMapObjectEmpty
U
Solution
extension TextUtilsStringExtension on String {
/// Returns true if string is:
/// - null
/// - empty
/// - whitespace string.
///
/// Characters considered "whitespace" are listed [here](https://stackoverflow.com/a/59826129/10830091).
bool get isNullEmptyOrWhitespace =>
this == null || this.isEmpty || this.trim().isEmpty;
}
/// - [isNullOrEmpty], [isNullEmptyOrFalse], [isNullEmptyZeroOrFalse] are from [this StackOverflow answer](https://stackoverflow.com/a/59826129/10830091)
extension GeneralUtilsObjectExtension on Object {
/// Returns true if object is:
/// - null `Object`
bool get isNull => this == null;
/// Returns true if object is NOT:
/// - null `Object`
bool get isNotNull => this != null;
/// Returns true if object is:
/// - null `Object`
/// - empty `String`s
/// - empty `Iterable` (list, set, ...)
/// - empty `Map`
bool get isNullOrEmpty =>
isNull ||
_isStringObjectEmpty ||
_isIterableObjectEmpty ||
_isMapObjectEmpty;
/// Returns true if object is:
/// - null `Object`
/// - empty `String`
/// - empty `Iterable` (list, map, set, ...)
/// - false `bool`
bool get isNullEmptyOrFalse =>
isNull ||
_isStringObjectEmpty ||
_isIterableObjectEmpty ||
_isMapObjectEmpty ||
_isBoolObjectFalse;
/// Returns true if object is:
/// - null `Object`
/// - empty `String`
/// - empty `Iterable` (list, map, set, ...)
/// - false `bool`
/// - zero `num`
bool get isNullEmptyFalseOrZero =>
isNull ||
_isStringObjectEmpty ||
_isIterableObjectEmpty ||
_isMapObjectEmpty ||
_isBoolObjectFalse ||
_isNumObjectZero;
// ------- PRIVATE EXTENSION HELPERS -------
/// **Private helper**
///
/// If `String` object, return String's method `isEmpty`
///
/// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `String`
bool get _isStringObjectEmpty =>
(this is String) ? (this as String).isEmpty : false;
/// **Private helper**
///
/// If `Iterable` object, return Iterable's method `isEmpty`
///
/// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `Iterable`
bool get _isIterableObjectEmpty =>
(this is Iterable) ? (this as Iterable).isEmpty : false;
/// **Private helper**
///
/// If `Map` object, return Map's method `isEmpty`
///
/// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `Map`
bool get _isMapObjectEmpty => (this is Map) ? (this as Map).isEmpty : false;
/// **Private helper**
///
/// If `bool` object, return `isFalse` expression
///
/// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `bool`
bool get _isBoolObjectFalse =>
(this is bool) ? (this as bool) == false : false;
/// **Private helper**
///
/// If `num` object, return `isZero` expression
///
/// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `num`
bool get _isNumObjectZero => (this is num) ? (this as num) == 0 : false;
}
Assumptions
This presumes Dart 2.7 or above to support Extension Methods.
Usage
The above are two Extension classes. One for Object
and one for String
. Put them in a file separately/together and import the files/file at callsite file.
Description
Coming from Swift, I tend to use extensions like user @Benjamin Menrad's answer but with getter instead of method/function when applicable -- mirroring Dart's computed properties like String.isEmpty
.
Coming from C#, I like their String's helper method IsNullOrWhiteSpace.
My version merges the above two concepts.
Naming
Rename the Extension classes whatever you like. I tend to name them like this:
XYExtension
Where:
- X is File/Author/App/Unique name.
- Y is name of Type being extended.
Name examples:
MyAppIntExtension
DartDoubleExtension
TextUtilsStringExtension
OHProviderExtension
Criticism
Why private getters instead of simple
this == null || this == '' || this == [] || this == 0 || !this
- I think this is safer as it first casts the object to the right type we want to compare its value to.
- More modular as changes are central within the private getters and any modification is reflected everywhere.
- If type check fails within the private getter, we return false which doesn't affect the outcome of the root logical-OR expression.