Late 2020 Update
Summary
- This answer holds true, except for
isNull
and isNotNull
. They no longer provide type promotion when Null 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
and isNotNull
but keep other helpers as you shouldn't expect them to do type promotion for you.
Explanation
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://mcmap.net/q/194261/-dart-null-false-empty-checking-how-to-write-this-shorter).
bool get isNullEmptyOrWhitespace =>
this == null || this.isEmpty || this.trim().isEmpty;
}
/// - [isNullOrEmpty], [isNullEmptyOrFalse], [isNullEmptyZeroOrFalse] are from [this StackOverflow answer](https://mcmap.net/q/194261/-dart-null-false-empty-checking-how-to-write-this-shorter)
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.
routeinfo["no_route"] != false
you meanrouteinfo["no_route"] == false
, right? β Botulinus