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.
Related
Future<void> foo() {
final futureInt = Future.value(2);
return futureInt;
}
How is the above code possible? the return value is a Future of type int, while the specified value of foo is Future of type void?
Also, when adding the async keyword to the function, it doesn't even run anymore (using dartpad), why is that? (check attachment)
the error is : A value of type 'Future' can't be returned from the function 'foo' because it has a return type of 'Future'.
I tried return the Future value directly and the error disappears:
Future<void> foo() async {
final futureInt = Future.value(2);
return Future.value(2);
}
No error from this code. I can't seem to understand the difference here also between returning futureInt (which gives an error) and Future.value(2) (which works perfectly)
Thanks in advance.
In Dart, void is essentially a top-type, meaning that all values can in theory be assigned to void. This is why a Future<int> is a subtype of a Future<void> and can be returned in the non-async method.
However, when void is used as a return type it gets a special meaning: It indicates that the method should not return a value. The compiler checks this, which is why it's an error to write things like:
void foo() {
return 2;
}
As far as the compiler is generally concerned, a Future<void> is not the same thing as void, so you can return a value in the non-asynchronous function.
With async functions, the situation is a bit different. They have to return a future, and so the compiler applies something known as "flattening": In an asynchronous method returning a Future<T>, the language mandates that either a Future<T> or a direct T is returned. So, in asynchronous functions, the Future<void> behaves like a void in synchronous functions. This is why it's suddenly forbidden to return the Future<int>.
When returning a Future.value(2), you're not directly specifying the future's type. By looking at the expected return type (which is Future<void>), the compiler infers that you're returning a Future<void>.value(2). This is perfectly legal because here, the 2 is not returned directly and int is a subtype of void.
As you'd expect, the following snippet is indeed forbidden:
Future<void> foo() async {
final futureInt = Future.value(2);
return Future<int>.value(2);
}
So, to summarize:
When not used as a return value, anything can be assigned to void.
Methods declared to return void can't return non-void values.
async methods returned to Future<void> also can't return non-void values.
I am trying to up-cast the subclass object but it is not working.
The following program compiles without any errors.
VideoStreamModel model = VideoStreamModel("");
VideoStream entity = model;
print(model); // prints VideoStreamModel
print(entity); // prints VideoStreamModel
print(entity as VideoStream); // prints VideoStreamModel
print(cast<VideoStream>(model)); // prints VideoStreamModel
I have written a testcase to test the relation of above two classes and it passes.
test('should be a subtype of VideoStream', () async {
expect(model, isA<VideoStream>());
});
What could be the problem here?
EDIT:
[deleted]
EDIT 2:
[deleted]
Edit 3:
Here is the complete code reproducing the error.
import 'package:equatable/equatable.dart';
import 'package:test/test.dart';
class A extends Equatable {
final String x;
A(this.x);
#override
List<Object> get props => [x];
}
class B extends A {
B(String x) : super(x);
A method() {
B b = B(x); // doing A b = A(x) makes the test pass
return b;
}
}
void main() {
B b = B("");
test('test', () async {
final expected = A(b.x);
final actual = b.method();
expect(actual, expected);
});
}
It generates the following assertion error:
Expected: A:<A>
Actual: B:<B>
print is calling the toString() on the object you are pointing at (in this case VideoStreamModel) which knows what type it is. When you are casting, you are not changing anything about the object itself but only how the compiler should see the object when it determines if you are allowed to use a given typed variable to point to the object.
So when you are doing entity as VideoStream you are really just telling the compiler that you "promise" that the entity can be seen as a VideoStream. But on runtime, this cast will be tested to see if it is true.
All of this is really not an issue since you should never test for the specific type of the object when you are programming Dart but instead use the is operator which tests if a given object is compatible with a given interface.
So e,g, (entity is VideoStream) will return true.
Updated part
You problem seems to be a misunderstanding of the use of Equatable. It is important to notice that Equatable are not only using the elements from props to determine if two objects are equal but it also looks at the runtimeType. You can see this from the implementation:
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Equatable &&
runtimeType == other.runtimeType &&
equals(props, other.props);
https://github.com/felangel/equatable/blob/master/lib/src/equatable.dart#L46
This means that:
A a = A("");
B b = B("");
print(a == b); // false
When you are using expect without any matcher, it will just make an == operation which is stated in the documentation:
matcher can be a value in which case it will be wrapped in an equals matcher
Since we (as stated before) cannot change the runtimeType of an object after its creation you need to implement your own == if you want the two object instances to be seen as equal since equatable does only see two objects as equal if they both is created from the same class and contains the same values defined with props.
I am writing a native plugin that, in some cases, has to call functions in the Flutter portion of the app, written in Dart.
How it's achieved, is explained here:
https://flutter.io/platform-channels/
Furthermore, an example of invoking a method from the native/platform part towards the Dart/non-native is here:
https://github.com/flutter/plugins/tree/master/packages/quick_actions
Now, this example is really nice in case the platform only needs to invoke a method, i.e. that call returns nothing/void, but in case it needs to invoke a function, i.e. needs a return value from the non-native/Dart part, I could not have found an example or documentation on the internet. I believe it can be implemented though, because in the native Java part, there is a method:
public void invokeMethod(String method, Object arguments, MethodChannel.Result callback)
So, there is a callback object that could have a return value from the non-native part - or, I am mistaken here, and there is currently no way of returning a value from the non-native Dart portion of the app?
The signature is void setMethodCallHandler(Future<dynamic> handler(MethodCall call)), so we need to provide a function at the Dart end that returns Future<dynamic>, for example _channel.setMethodCallHandler(myUtilsHandler);
Then implement the handler. This one handles two methods foo and bar returning respectively String and double.
Future<dynamic> myUtilsHandler(MethodCall methodCall) async {
switch (methodCall.method) {
case 'foo':
return 'some string';
case 'bar':
return 123.0;
default:
throw MissingPluginException('notImplemented');
}
}
At the Java end the return value is passed to the success method of the Result callback.
channel.invokeMethod("foo", arguments, new Result() {
#Override
public void success(Object o) {
// this will be called with o = "some string"
}
#Override
public void error(String s, String s1, Object o) {}
#Override
public void notImplemented() {}
});
In Swift, the return value is an Any? passed to the result closure. (Not implemented is signaled by the any parameter being the const NSObject value FlutterMethodNotImplemented.)
channel.invokeMethod("foo", arguments: args, result: {(r:Any?) -> () in
// this will be called with r = "some string" (or FlutterMethodNotImplemented)
})
I am trying to create a Dart function that essentially wraps other functions with some boilerplate error handling code, and otherwise returns the value returned by the original function. A key requirement is that it should accept functions with multiple different return types, while avoiding duplicating the common error handling logic across multiple different functions. I found one approach that seems to work by using the dynamic type, except that the compiler is not able to detect type mismatches, so they are only caught at runtime.
Is there a better way to accomplish what I'm aiming for here, and particularly in a way that catches type mismatches at compile time?
Below is a simplified example of my code, where the functions compile fine, but at runtime getAString will raise an error Dart Error: Unhandled exception: type 'List<String>' is not a subtype of type 'String'
/// Signature of API function calls
typedef APIFunctionCall = dynamic Function();
dynamic doWithErrorHandling(APIFunctionCall fn, {retries: 2}) async {
for (int attempts = 0; attempts < retries + 1; attempts++) {
try {
return await fn();
}
on Exception catch (e) {
print(
"This is just an example; actual function does a bunch of more specific error handling.");
}
}
}
Future<String> getAString() async {
// Want a function that can support multiple return types but detect type errors
String doesReturnAString = await doWithErrorHandling(() async => 'hello world'); // This runs fine
String doesntReturnAString = await doWithErrorHandling(() async => <String>['hello', 'world']); // This throws an Error
return doesntReturnAString;
}
You can abstract over the return type using a type parameter:
Future<T> doWithErrorHandling<T>(Future<T> fn(), {int retries = 2}) async {
do {
try {
return await fn();
} catch (e) {
// record error.
}
retries--;
} while (retries >= 0);
return null; // or whatever.
}
With that, you can call with any function. In most cases, the type argument can be inferred from the static type of the argument function, or from the type expected by the surrounding context, but if not, you can write it yourself.
Future<String> getAString() async {
String doesReturnAString = await doWithErrorHandling(() async => 'hello world');
// The next line has a compile-time type error!
String doesntReturnAString = await doWithErrorHandling(() async => <String>['hello', 'world']);
return doesntReturnAString;
}
(As an unrelated hint, you should never catch Exception. Dart errors do not implement Exception, they implement Error. Exception is a meaningless marker interface used by some thrown objects that the user is intended to catch and handle, but in that case, you should be catching the particular exception, like on FormatException, not the plain Exception. So, general rule: Never write on Exception).
In Dart, is it possible for a function to have a prototype associated with it?
Example Javascript code:
doStuff.prototype.isDefined = true; //is there anything like Javascript's function prototypes in Dart?
function doStuff(){
console.log("The function doStuff was called!");
}
Is it possible to do the equivalent of this in Dart (i.e., create a list of properties for each function?)
Two things to address here:
First, Dart doesn't have prototypes or prototypal inheritance, and instead uses classical inheritance. Rather than a prototype, objects have a class, and instead of a prototype chain, objects have superclasses.
Second, for your specific case, I think we'd have to see more of what you need to do to figure out the idiomatic way to do it in Dart. It should soon be possible to emulate functions with objects so that you can invoke an object and still have state and other methods associated with it.
See this article for more: http://www.dartlang.org/articles/emulating-functions/
When that capability lands you'll be able to do this:
class DoStuff {
bool isDefined = true;
call() => print("The function doStuff was called!");
}
var doStuff = new DoStuff();
main() => doStuff();
Which works if you have a fixed set of metadata about your function that you need to keep track of. It's slightly different from JavaScript because each instance of the function in Dart will have its own state for isDefined. I'm not sure if it's possible or easy to get multiple instances of the function in JavasScript, but you might need to make isDefined static so that the value is shared across all instances.
Dart does not allow you to add or remove member variables from an instance of a class at runtime. Rewriting your example in Dart it might look something like this:
class doStuff {
bool isDefined;
doStuff() {
isDefined = true;
}
void stuff() {
print('The function stuff was called!');
}
}
main() {
new doStuff().stuff();
}
If you wanted to add a property bag to a class in Dart you would write:
class PropertyObject {
Map<String, Dynamic> properties;
PropertyObject() {
properties = new Map<String, Dynamic>();
}
Dynamic operator[](String K) => properties[K];
void operator[]=(String K, Dynamic V) => properties[K] = V;
}
main() {
PropertyObject bag = new PropertyObject();
bag['foo'] = 'world';
print('Hello ${bag['foo']}');
}
Note that you can't access map properties using the '.' operator.