Learning Dart and using dart_code_metrics to ensure that I write code that meets expectations. One of the rules that is active is avoid-non-null-assertion.
Note, the code below was created to recreate the problem encountered in a larger code base where the value of unitString is taken from a JSON file. As such the program cannot control what is specified in the JSON file.
From pubspec.yaml
environment:
sdk: '>=2.15.0 <3.0.0'
// ignore_for_file: avoid_print
import 'package:qty/qty.dart';
void main() {
const String unitString = 'in';
// unit.Width returns null if unitString is not a unit of Length.
if (Length().unitWith(symbol: unitString) == null) {
print('units $unitString not supported.');
} else {
// The following line triggers avoid-non-null-assertion with the use of !.
final Unit<Length> units = Length().unitWith(symbol: unitString)!;
final qty = Quantity(amount: 0.0, unit: units);
print('Qty = $qty');
}
}
If I don't use ! then I get the following type error:
A value of type 'Unit<Length>?' can't be assigned to a variable of type 'Unit<Length>'.
Try changing the type of the variable, or casting the right-hand type to 'Unit<Length>'.
Casting the right-hand side to
Unit<Length>
fixes the above error but cause a new error when instantiating Quantity() since the constructor expects
Unit<Length>
and not
Unit<Length>?
I assume there is an solution but I'm new to Dart and cannot formulate the correct search query to find the answer.
How can I modify the sample code to make Dart and dart_code_metrics happy?
Your idea of checking for null before using a value is good, it's just not implemented correctly. Dart does automatically promote nullable types to non-null ones when you check for null with an if, but in this case you need to use a temporary variable.
void main() {
const String unitString = 'in';
//Use a temp variable, you could specify the type instead of using just using final
final temp = Length().unitWith(symbol: unitString);
if (temp == null) {
print('units $unitString not supported.');
} else {
final Unit<Length> units = temp;
final qty = Quantity(amount: 0.0, unit: units);
print('Qty = $qty');
}
}
The basic reason for that when you call your unitWith function and see that it's not null the first time, there's no guarantee that the when you call it again that it will still return a non-null value. I think there's another SO question that details this better, but I can't seem to find.
I have a future that has a generic parameter, which is a superclass (A) of another class (B extends A). I know for a fact that the instance of the value of the Future is of the subtype. Why can't I downcast the Future<A> to Future<B> in dart? If I unwrap the Future once and then wrap it again using async/await, it works.
Here's an example:
class A {}
class B extends A{}
void main() {
Future<A> getFuture() async { return B();}
Future<B> getBasA() { return getFuture() as Future<B>;}
Future<B> getBasAasync() async { return (await getFuture()) as B;}
print(getBasAasync()); // Works
print(getBasA()); // Throws at runtime
}
For the curious and as a motivation for the question, here's a closer-to-world example. I have a stream that emits data packets, which I filter and then get the first like this:
Future<T> getResponse<T extends ReceivedPacket>() =>
getStream<ReceivedPacket>().firstWhere((packet) => packet is T) as Future<T>; //throws
Future<T> getResponse<T extends ReceivedPacket>() async { //works
return (await getStream<ReceivedPacket>().firstWhere((packet) => packet is T)) as T;
}
PS: I've tried it out in Typescript (will happily compile and run) and C# (won't compile, but I have very limited C# knowledge). I understand that the answer to this question might be "because this is how the dart type system works". I'm just confused, because I'd have expected it either to fail at compile time like C# or work at runtime, too, like typescript.
You declared getFuture() as returning Future<A> but with the async keyword, so Dart automatically transforms return B(); to (essentially) return Future<A>.value(B());. The returned Future was never a Future<B> and therefore cannot be downcast to it.
Creating the Future explicitly would do what you expect:
Future<A> getFuture() { return Future<B>.value(B()); }
You could argue that Dart when transforms return x; in async functions, it should create a Future<T> where T is the static type of x instead of inferring it from the function's return type. As lrn explained in comments, that can't be done because you might have:
class C extends A {}
Future<A> getFuture() async {
await Future.delayed(const Duration(seconds: 1));
if (Random().nextBool()) {
return B();
} else {
return C();
}
}
The caller must get a Future back immediately, but it won't be known whether its value will be a B or C until the Future eventually completes.
I'd have expected it either to fail at compile time like C#
I too have very limited experience with C#, but I think that C# gives you a compilation error because C# does not consider Generic<SubType> to be a subtype of Generic<SuperType> whereas Dart does.
void main() => Foo() == 1;
class Foo {
#override
bool operator ==(Object other) {
print(super == this); // true
// print(this == super); // Compile error
return super == other;
}
}
There are 2 questions.
Why can't I do this == super?
If you look at the return statement return super == other, you can tell the operator == is called on the Object class, so is the entire == implementation not a call which has been delegated to the Object class?
Let me explain the 2nd question further if it wasn't clear. Let's say I check for
Foo() == 1
The == operator defined in Foo class will be invoked and there all I'm doing is return super == other that means Object's == operator is being invoked and the other is int, so where the Foo instance in above code is? Here, it is only Object and int. I think super is doing something else too, not sure what is.
To answer your questions:
You can't do this == super because super is not an expression which is explained if you try run the program (the analyzed could give a better explanation):
bin/stackoverflow.dart:7:19: Error: Can't use 'super' as an expression.
To delegate a constructor to a super constructor, put the super call as an initializer.
print(this == super); // Compile error
^^^^^
Which also make sense since super is not an object. It is a concept we can use to specify if we want to refer to fields/methods that are overridden. But we cannot use super as some kind of object since our object is not multiple objects in layers where you can extract a layer and then represent this as another object. Instead, our object is a combined implementation based on how you define your class.
Yes, you're basically calling the Object's == operator when you return super == other.
Supplementary example:
But remember, we are just calling the == operator defined in Object. Our object is still a combined data structure which can be illustrated by this example:
class A {
final int a = 5;
int getA() => a;
}
class B extends A {
int get a => 10;
int getA() {
return super.getA();
}
}
void main() {
print(B().getA()); // 10
}
You can see that even if we call super.getA() we are still operating on a B object where we have overridden the a field but we are calling the A.getA() method.
I have a class in AngularDart as followings:
abstract class Validator {
Map validate(AbstractControl c);
}
Looking closely, this used to be (before we added strong-mode support):
abstract class Validator {
validate(AbstractControl c);
}
The issue that it technically supports returning a Future or Map.
I'd like to refactor this and properly type it using FutureOr:
abstract class Validator {
FutureOr<T> validate(AbstractControl c);
}
Will I be able to use an is check at runtime? (In DDC and dart2js)
void runValidator(Validator v) {
var result = v.validate(...);
if (result is Future) {
// async...
} else {
// sync...
}
}
Am I thinking about this correctly?
EDIT: As mentioned below, I mean
if (result is Future<T>) {
} else if (result is T) {
}
One more question, would validate match these two typedefs:
Future<Map> AsyncValidate(AbstractControl c);
Map SyncValidate(AbstractControl c);
Yes, you can do result is Future<Map>. The actual value returned by the validate method is either a Future or it's not. The static type of the function doesn't affect that, and since FutureOr<Map> isn't an actual class, you can't have an object that is "a FutureOr". It's either a real Future<Map> or it's a Map.
For the second question, that depends on what yo mean by "match".
You can override the method with a method that returns either Map or FutureMap:
abstract class Validator {
FutureOr<Map> validate(abstractControl c);
}
class AsyncValidator extends Validator {
Future<Map> validate(AbstractControl c) {...}
}
class SyncValidator extends Validator {
Map validate(AbstractControl c) {...}
}
That is, you can use one of the function types you mention as a Validator.validate, but not in the other direction.
typedef FutureOr<Map> Validate(AbstractControl c);
typedef Future<Map> AsyncValidate(AbstractControl c);
typedef Map SyncValidate(AbstractControl c);
Validator v = ...;
Validate f0 = v.validate; // Safe.
AsyncValidate f1 = v.validate; // BAD assignment.
SyncValidate f2 = v.validate; // BAD assignment.
Map syncValidate(AbstractControl c) { ... }
Future<Map> asyncValidate(AbstractControl c) { ... }
v = syncValidate; // Good assignment.
v = asyncValidate; // Good assignment.
In practice, the concrete validate method of the validator v will probably be assignable to one of f1 or f2, but its static type doesn't say which one, so both are considered bad assignments.
You should only very rarely have a non-abstract method that is declared as returning FutureOr. In most cases, it's better to just always return a Future or a non-Future, and declare the method as such. Then you can always use the function as returning FutureOr if you need to, but use the more precise type in cases where you need it.
I would like to pass a primitive (int, bool, ...) by reference. I found a discussion about it (paragraph "Passing value types by reference") here: value types in Dart, but I still wonder if there is a way to do it in Dart (except using an object wrapper) ? Any development ?
The Dart language does not support this and I doubt it ever will, but the future will tell.
Primitives will be passed by value, and as already mentioned here, the only way to 'pass primitives by reference' is by wrapping them like:
class PrimitiveWrapper {
var value;
PrimitiveWrapper(this.value);
}
void alter(PrimitiveWrapper data) {
data.value++;
}
main() {
var data = new PrimitiveWrapper(5);
print(data.value); // 5
alter(data);
print(data.value); // 6
}
If you don't want to do that, then you need to find another way around your problem.
One case where I see people needing to pass by reference is that they have some sort of value they want to pass to functions in a class:
class Foo {
void doFoo() {
var i = 0;
...
doBar(i); // We want to alter i in doBar().
...
i++;
}
void doBar(i) {
i++;
}
}
In this case you could just make i a class member instead.
No, wrappers are the only way.
They are passed by reference. It just doesn't matter because the "primitive" types don't have methods to change their internal value.
Correct me if I'm wrong, but maybe you are misunderstanding what "passing by reference" means? I'm assuming you want to do something like param1 = 10 and want this value to still be 10 when you return from your method. But references aren't pointers. When you assign the parameter a new value (with = operator), this change won't be reflected in the calling method. This is still true with non-primitive types (classes).
Example:
class Test {
int val;
Test(this.val);
}
void main() {
Test t = new Test(1);
fn1(t);
print(t.val); // 2
fn2(t);
print(t.val); // still 2, because "t" has been assigned a new instance in fn2()
}
void fn1(Test t) {
print(t.val); // 1
t.val = 2;
}
void fn2(Test t) {
t = new Test(10);
print(t.val); // 10
}
EDIT
I tried to make my answer more clear, based on the comments, but somehow I can't seem to phrase it right without causing more confusion. Basically, when someone coming from Java says "parameters are passed by reference", they mean what a C/C++ developer would mean by saying "parameters are passed as pointers".
As dart is compiled into JavaScript, I tried something that works for JS, and guess what!? It worked for dart!
Basically, what you can do is put your value inside an object, and then any changes made on that field value inside that function will change the value outside that function as well.
Code (You can run this on dartpad.dev)
main() {
var a = {"b": false};
print("Before passing: " + a["b"].toString());
trial(a);
print("After passing: " + a["b"].toString());
}
trial(param) {
param["b"] = true;
}
Output
Before passing: false
After passing: true
One of the way to pass the variables by reference by using the values in List. As arrays or lists are Pass by reference by default.
void main() {
List<String> name=['ali' ,'fana'];
updatename(name);
print(name);
}
updatename(List<String> name){
name[0]='gufran';
}
Try this one, This one of the simplest way to pass by reference.
You can use ValueNotifier
And, you can pass it as ValueListenable to classes or methods that needs to know up-to-date value, but should not edit it:
class Owner {
final theValue = ValueNotifier(true);
final user = User(theValue);
...
}
class User {
final ValueListeneble<bool> theValue;
User(this.theValue);
...
}
It provides more functionality than actually needed, but solves the problem.
If ValueNotifier + ValueListenable do not work for you (you want to make sure the client does not listen to every change of the value, or your package is pure Dart package and thus cannot reference Flutter libraries), use a function:
class Owner {
int _value = 0;
int getValue() => _value;
void increase() => _value++;
}
void main() {
final owner = Owner();
int Function() obtainer = owner.getValue;
print(obtainer());
owner.increase();
print(obtainer());
}
Output will be:
0
1
This approach has memory usage related downside: the obtainer will hold the reference to the owner, and this, even if owner is already not referenced, but obtainer is still reachable, owner will be also reachable
and thus will not be garbage collected.
If you do not want the downside, pass the smaller container than the entire owner:
import 'package:flutter/foundation.dart';
class ListenableAsObtainer<T> implements ValueObtainer<T> {
ListenableAsObtainer(this._listenable);
final ValueListenable<T> _listenable;
#override
T get value => _listenable.value;
}
class FunctionAsObtainer<T> implements ValueObtainer<T> {
FunctionAsObtainer(this._function);
final T Function() _function;
#override
T get value => _function();
}
class ValueAsObtainer<T> implements ValueObtainer<T> {
ValueAsObtainer(this.value);
#override
T value;
}
/// Use this interface when the client needs
/// access to the current value, but does not need the value to be listenable,
/// i.e. [ValueListenable] would be too strong requirement.
abstract class ValueObtainer<T> {
T get value;
}
The usage of FunctionAsObtainer will still result in holding the owner from garbage collection, but two other options will not.
Just to make it clear:
void main() {
var list1 = [0,1,2];
var modifiedList1 = addMutable(list1, 3);
var list2 = [0,1,2];
var modifiedList2 = addImmutable(list2, 3);
print(list1);
print(modifiedList1);
print(list2);
print(modifiedList2);
}
List<int> addMutable(List<int> list, int element){
return list..add(element);
}
List<int> addImmutable(List<int> list, int element){
return [...list, element];
}
Output:
[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2]
[0, 1, 2, 3]
All variables are passed by value. If a variable contains a primitive (int, bool, etc.), that's it. You got its value. You can do with it whatever you want, it won't affect the source value. If a variable contains an object, what it really contains is a reference to that object.
The reference itself is also passed by value, but the object it references is not passed at all. It just stayed where it was. This means that you can actually make changes to this very object.
Therefore, if you pass a List and if you .add() something to it, you have internally changed it, like it is passed by reference. But if you use the spread operator [...list], you are creating a fresh new copy of it. In most cases that is what you really want to do.
Sounds complicated. Isn't really. Dart is cool.