Avoid duplication of null type checking in Dart - flutter

My current goal is to remove this code duplication:
final int? myNullableInt = 10;
/// Everywhere I need to do this null verification:
if (myNullableInt == null) return null;
return someOtherMethodThatReceivesANonNullableInt(myNullableInt);
I want to convert to something like we have in Kotlin:
final int? myNullableInt = 10;
return myNullableInt?.apply((myInt) => someOtherMethodThatReceivesANonNullableInt(myInt));
I did it:
extension ApplyIfNotNull on Object? {
T? apply<T extends Object?>(Object? obj, T? Function(Object) fn) {
if (obj == null) return null;
return fn(obj);
}
}
But this gives me a static error:
The argument type 'Object' can't be assigned to the parameter type 'int'.
Note: this should work with all types, e.g ints, Strings, double and MyOwnClassTypes.
Is there something I can do? or am I missing something?

extension ApplyIfNotNull on Object? {
T? apply<T extends Object?>(Object? obj, T? Function(Object) fn) {
if (obj == null) return null;
return fn(obj);
}
}
That doesn't work because it declares that the callback be capable of accepting any Object argument, but you're presumably trying to use it with a function that accepts only an int argument. It's also unclear why you've made an extension method since it doesn't involve the receiver (this) at all.
You need to make your function generic on the callback's argument type as well:
R? applyIfNotNull<R, T>(T? obj, R Function(T) f) =>
(obj == null) ? null : f(obj);
(That's the same as what I suggested in https://github.com/dart-lang/language/issues/360#issuecomment-502423488 but with the arguments reversed.)
Or, as an extension method, so that it can work on this instead of having the extra obj argument:
extension ApplyIfNotNull<T> on T? {
R? apply<R>(R Function(T) f) {
// Local variable to allow automatic type promotion. Also see:
// <https://github.com/dart-lang/language/issues/1397>
var self = this;
return (self == null) ? null : f(self);
}
}
Also see https://github.com/dart-lang/language/issues/360 for the existing language feature request and for some other suggested workarounds in the meantime.

Related

Flutter/Dart: Why does this statement effect null-nafety?

I realized this is just fine:
List<int> list = [];
int? b;
void addBarIfNull() {
if (b != null) {
list.add(b); // no problem
}
}
But adding this one statement b = null after adding b to the list, lets the linter complain with: The argument type 'int?' can't be assigned to the parameter type 'int':
List<int> list = [];
int? b;
void addBarIfNull() {
if (b != null) {
list.add(b); // problem: The argument type 'int?' can't be assigned to the parameter type 'int'
b = null;
}
}
Can someone explain what is going on here? Why is b considered as int? in list.add(b) if we clearly check before that it is not null? Why do both code snippets differ in handling this?
It is null safey issue when you declare
{
int? b;
}
you allow b to be null or have value but when you try to add
{
list.add(b);
}
b to the list you don't allow the list to contain null value that why the error message said you can't add int? to int you need to allow the list to contain null value if that whta you needed.
{
List<int?> list =[];
}

How to check if FileSystemEntity is a Directory [duplicate]

Let's say I have:
class Test<T> {
void method() {
if (T is int) {
// T is int
}
if (T == int) {
// T is int
}
}
}
I know I can override == operator but what's the main difference between == and is in Dart if I don't override any operator.
Edit:
Say I have
extension MyIterable<T extends num> on Iterable<T> {
T sum() {
T total = T is int ? 0 : 0.0; // setting `T == int` works
for (T item in this) {
total += item;
}
return total;
}
}
And when I use my extension method with something like:
var addition = MyIterable([1, 2, 3]).sum();
I get this error:
type 'double' is not a subtype of type 'int'
identical(x, y) checks if x is the same object as y.
x == y checks whether x should be considered equal to y. The default implementation for operator == is the same as identical(), but operator == can be overridden to do deep equality checks (or in theory could be pathological and be implemented to do anything).
x is T checks whether x has type T. x is an object instance.
class MyClass {
MyClass(this.x);
int x;
#override
bool operator==(dynamic other) {
return runtimeType == other.runtimeType && x == other.x;
}
#override
int get hashCode => x.hashCode;
}
void main() {
var c1 = MyClass(42);
var c2 = MyClass(42);
var sameC = c1;
print(identical(c1, c2)); // Prints: false
print(identical(c1, sameC)); // Prints: true
print(c1 == c2); // Prints: true
print(c1 == sameC); // Prints: true
print(c1 is MyClass); // Prints: true
print(c1 is c1); // Illegal. The right-hand-side must be a type.
print(MyClass is MyClass); // Prints: false
}
Note the last case: MyClass is MyClass is false because the left-hand-side is a type, not an instance of MyClass. (MyClass is Type would be true, however.)
In your code, T is int is incorrect because both sides are types. You do want T == int in that case. Note that T == int would check for an exact type and would not be true if one is a derived type of the other (e.g. int == num would be false).
Basically, == is equality operator and "is" is the instanceof operator of Dart (If you come from Java background, if not, it basically tells you if something is of type something).
Use == for equality, when you want to check if two objects are equal. You can implement the == operator (method) in your class to define on what basis do you want to judge if two objects are equal.
Take this example:
class Car {
String model;
String brand;
Car(this.model, this.brand);
bool operator == (otherObj) {
return (otherObj is Car && other.brand == brand); //or however you want to check
//On the above line, we use "is" to check if otherObj is of type Car
}
}
Now you can check if two cars are "equal" based on the condition that you have defined.
void main() {
final Car micra = Car("Micra", "Nissan");
print(micra == Car("Micra", "Nissan")); // true
print(micra is Car("Micra", "Nissan")); // true
}
Hence, == is something you use to decide if two objects are equal, you can override and set it as per your expectations on how two objects should be considered equal.
On the other hand, "is" basically tells you if an instance is of type object (micra is of type Car here).

Why does the dart "is" operator behave differently for local variables vs. class fields? [duplicate]

This question already has answers here:
"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety
(3 answers)
Closed 12 months ago.
I have migrated my Dart code to NNBD / Null Safety. Some of it looks like this:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
This causes two analysis errors. For _a += 'a';:
An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.
For Bar() {:
Non-nullable instance field '_a' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
In both cases I have already done exactly what the error suggests! What's up with that?
I'm using Dart 2.12.0-133.2.beta (Tue Dec 15).
Edit: I found this page which says:
The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.
But that doesn't make sense to me - there's only one possible flow control path from if (_a != null) to _a += 'a'; in this case - there's no async code and Dart is single-threaded - so it doesn't matter that _a isn't local.
And the error message for Bar() explicitly states the possibility of initialising the field in the constructor.
The problem is that class fields can be overridden even if it is marked as final. The following example illustrates the problem:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
#override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
The B class overrides the text final field so it returns a value the first time it is asked but returns null after this. You cannot write your A class in such a way that you can prevent this form of overrides from being allowed.
So we cannot change the return value of getText from String? to String even if it looks like we checks the text field for null before returning it.
An expression whose value can be 'null' must be null-checked before it can be dereferenced. Try checking that the value isn't 'null' before dereferencing it.
It seems like this really does only work for local variables. This code has no errors:
class Foo {
String? _a;
void foo() {
final a = _a;
if (a != null) {
a += 'a';
_a = a;
}
}
}
It kind of sucks though. My code is now filled with code that just copies class members to local variables and back again. :-/
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
Ah so it turns out a "field initializer" is actually like this:
class Bar {
Bar() : _a = 'a';
String _a;
}
There are few ways to deal with this situation. I've given a detailed answer here so I'm only writing the solutions from it:
Use local variable (Recommended)
void foo() {
var a = this.a; // <-- Local variable
if (a != null) {
a += 'a';
this.a = a;
}
}
Use ??
void foo() {
var a = (this.a ?? '') + 'a';
this.a = a;
}
Use Bang operator (!)
You should only use this solution when you're 100% sure that the variable (a) is not null at the time you're using it.
void foo() {
a = a! + 'a'; // <-- Bang operator
}
To answer your second question:
Non-nullable fields should always be initialized. There are generally three ways of initializing them:
In the declaration:
class Bar {
String a = 'a';
}
In the initializing formal
class Bar {
String a;
Bar({required this.a});
}
In the initializer list:
class Bar {
String a;
Bar(String b) : a = b;
}
You can create your classes in null-safety like this
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1, this.docCam2, this.docBarcode, this.docSignature});
JobDoc.fromJson(Map<String, dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}

How to create null safe block in flutter?

How do I null check or create a null safe block in Flutter?
Here is an example:
class Dog {
final List<String>? breeds;
Dog(this.breeds);
}
void handleDog(Dog dog) {
printBreeds(dog.breeds); //Error: The argument type 'List<String>?' can't be assigned to the parameter type 'List<String>'.
}
void printBreeds(List<String> breeds) {
breeds.forEach((breed) {
print(breed);
});
}
If you try to surround it with an if case you get the same error:
void handleDog(Dog dog){
if(dog.breeds != null) {
printBreeds(dog.breeds); //Error: The argument type 'List<String>?' can't be assigned to the parameter type 'List<String>'.
}
}
If you create a new property and then null check it it works, but it becomes bothersome to create new properties each time you want to null check:
void handleDog(Dog dog) {
final List<String>? breeds = dog.breeds;
if (breeds != null) {
printBreeds(breeds); // OK!
}
}
Is there a better way to do this?
Like the ?.let{} syntax in kotlin?
To get something similar to Kotlins .let{} i created the following generic extension :
extension NullSafeBlock<T> on T? {
void let(Function(T it) runnable) {
final instance = this;
if (instance != null) {
runnable(instance);
}
}
}
And it can be used like this:
void handleDog(Dog dog) {
dog.breeds?.let((it) => printBreeds(it));
}
"it" inside the let function will never be null at runtime.
Thanks to all the suggestions, but they were all some variation of moving the null check further down the code execution cain, which was not what i was looking for.
Yes, you'll have create a local variable just like you did to handle those things because if you don't create a local variable then if there is a class which is extending the Dog class can override breeds which will then become nullable even after you had checked it in the first place.
The other solution you can try is changing the List<String> to nullable in printBreeds method.
void handleDog(Dog dog) {
printBreeds(dog.breeds);
}
void printBreeds(List<String>? breeds) {
breeds?.forEach((breed) {
print(breed);
});
}
This error is right //Error: The argument type 'List<String>?' can't be assigned to the parameter type 'List<String>'.
as null type list is passing to function which says it accepts a non-null list
By below way breeds can be accessible
void printBreeds(List<String>? breeds) {
breeds?.forEach((breed) {
print(breed);
});
}
Also, if we don't want nullable operation every time, we can handle it while calling
Example:
class Dog {
final List<String>? breeds;
Dog(this.breeds);
}
void handleDog(Dog dog) {
print("handleDog");
printBreeds(dog.breeds!);
}
// This method only called if breeds not null
void printBreeds(List<String> breeds) {
print("printBreeds");
breeds.forEach((breed) {
print(breed);
});
}
void main() {
var dog = Dog(null);
handleDog(dog);
}
Output:
printBreeds

How to assign a function inline to a value in Dart?

I like to assign the return value of a function to a variable, but inline. The following is how you would do it not inline.
bool isValid() {
if(a == b) return true;
if(a == c) return true;
return false;
}
bool result = isValid();
What I want is something like
bool result = () {
if(a == b) return true;
if(a == c) return true;
return false;
}
However it displays the error
The argument type 'Null Function()' can't be assigned to the parameter type 'bool'
How do I achieve this?
You are defining a lambda expression. This works the same way as in Javascript, Typescript or many other languages.
bool result = () {
if(a == b) return true;
if(a == c) return true;
return false;
}
This code defines an anonymous function of type () -> bool (accepts no parameters and returns bool). And the actual type of the result variable is bool so the compilation is broken (() -> bool and bool are non-matching types).
To make it correct just call the function to get the result.
bool result = () {
if(a == b) return true;
if(a == c) return true;
return false;
}();
Now you define an anonymous function (lambda) and then call it so the result is bool. Types are matching and there's no error.
This is rather an uncommon behaviour to define the function and immediately call it. It's used in Javascript to create a separate scope for variables using closures (some kind of private variables). I would recommend you to move the code to some kind of class or pass a, b, c parameters directly to the function:
bool isValid(a, b, c) {
/* Your code */
}
It's more generic that way and could be reused. Immediately called lambda is often a sign of bad design.