I'm converting dart code to nnbd.
I have the following code.
var subscription = response.listen(
(newBytes) async {
/// if we don't pause we get overlapping calls from listen
/// which causes the [writeFrom] to fail as you can't
/// do overlapping io.
subscription.pause();
/// we have new data to save.
await raf.writeFrom(newBytes);
subscription.resume();
});
The problem is I get the following error:
The non-nullable local variable 'subscription' must be assigned before it can be used.
Try giving it an initializer expression, or ensure that it's assigned on every execution path.
I've had a similar problem solved here:
dart - correct coding pattern for subscription when using null saftey?
which was answered by #lrn
However the pattern solution pattern doesn't seem to work in this case.
raf.writeFrom is an async operation so I must use an 'async' method which means I can't use the 'forEach' solution as again I don't have access to the subscription object.
If you really want to use listen, I'd do it as:
var subscription = response.listen(null);
subscription.onData((newBytes) async {
subscription.pause();
await raf.writeFrom(newBytes);
subscription.resume();
});
or, without the async:
var subscription = response.listen(null);
subscription.onData((newBytes) {
subscription.pause(raf.writeFrom(newBytes));
});
which will pause the subscription until the future returned by raf.writeFrom completes (it shouldn't complete with an error, though).
If using listen is not a priority, I'd prefer to use an asynchronous for-in like:
await for (var newBytes in subscription) {
await raf.writeFrom(newBytes);
}
which automatically pauses the implicit subscription at the await and resumes it when you get back to the loop.
Both with stream.listen and the StreamController constructor, null safety has made it nicer to create them first without callbacks, and then add the callbacks later, if the callback needs to refer to the subscription/controller.
(That's basically the same nswer as in the linked question, only applied to onData instead of onDone. You have to pass a default onData argument to listen, but it can be null precisely to support this approach.)
I don't think your code, as written, was legal before null-safety either; you can't reference a variable (subscription) before it's declared, and the declaration isn't complete until after the expression you initialize it with (response.listen(...)) is evaluated. You will need to separate the declaration from the initialization to break the circular dependency:
StreamSubscription<List<int>> subscription;
subscription = response.listen(...);
Related
Currently I am learning the bloc library for Flutter and I stumbled across this Function, I may be too unfamiliar with the exact implementation of Futures in Dart.
Here I am required to use the async keyword, since I am using await.
I would like to return an Item but am not allowed to, since I used the async keyword and have to return a Future.
Is the Item being wrapped up in a Future "artificially", because I've awaited already, so what is the point of the return type being a Future?
Does this result in a small overhead?
Future<Item> getItem(String id) async{
Item item = await _itemApiClient.getItemById(id);
return item;
}
With this implementation I am able to use "await" simply one time; when awaiting the getItem() function.
Future<Item> getItem(String id) {
Future<Item> item = _itemApiClient.getItemById(id);
return item;
}
Your function is perfectly fine.
I'd even do:
Future<Item> item(String id) => _itemApiClient.getItemById(id);
Yes, there is an overhead in having an async function.
The async function always creates its own Future for its result, and there is an overhead moving the value from the getItemById-returned future to the future returned by getItem.
The overhead may or may not affect timing, depending on how the underlying futures are created. (It's possible that the value is propagated immediately when the first future completes, but there are no promises.) There is always a memory overhead.
I wrote a very small package for myself in order to execute futures sequentially, it does the following:
Ensures that futures are executed in the order they are "appended", each waiting for the previous future to finish.
Passes the last returned result to the next future being executed.
Returns the value of the appended future.
Here is the complete code of the package:
import 'dart:async';
class FutureQueue<T> {
final Duration? _timeLimit;
Future<T?> _future;
FutureQueue({Duration? timeLimit})
: _timeLimit = timeLimit,
_future = Future.value();
FutureQueue.seeded(
FutureOr<T> seed, {
Duration? timeLimit,
}) : _timeLimit = timeLimit,
_future = Future.value(seed);
Future<T?> get future => _future;
/// Adds the given [future] to queue. Note that the [previous] value will be
/// null if the last operation caused an exception.
Future<T?> append(FutureOr<T> Function(T? previous) future,
{Duration? timeLimit}) {
return _future = _future.catchError((_) {}).then((previous) {
if (timeLimit != null || _timeLimit != null) {
return _withTimeout(future(previous), timeLimit ?? _timeLimit!);
}
return future(previous);
});
}
Future<T> _withTimeout(FutureOr<T> future, Duration timeLimit) {
return Future.value(future).timeout(timeLimit);
}
/// Waits for queued events to finish.
Future<void> close() async {
await _future;
}
}
I've seen this error reported in production when calling append with a Future that returns a non-nullable value and then using the null-check operator to assert that it is not null:
_CastError: Null check operator used on a null value
File "api_token_repo.dart", line 37, in ApiTokenRepo.fetch.<fn>
File "zone.dart", line 1434, in _rootRunUnary
File "<asynchronous suspension>"
File "user_bloc.dart", line 169, in UserBloc._mapUserProfileReloadStarted
File "<asynchronous suspension>"
File "user_bloc.dart", line 71, in UserBloc._configureEventHandling.<fn>
File "<asynchronous suspension>"
File "bloc.dart", line 211, in Bloc.on.<fn>.handleEvent
The code where the exception is thrown is as follows:
Future<ApiToken> fetch() {
return _futureQueue
.append((_) => _fetchAndSave())
.then((apiToken) => apiToken!); // <- Exception thrown here
}
Future<ApiToken> _fetchAndSave() async {
final apiToken = await _fetcher();
_saver(apiToken);
return apiToken;
}
The exception is thrown when calling apiToken! and I can't understand how this is possible. My understanding is that if an exception was thrown when calling _fetchAndSave then the then call would be bypassed since an error occurred. If an error didn't occur then _fetchAndSave doesn't allow a non-null value to be returned.
My first impression was that perhaps it is a race condition where append is called twice and the second call is made before the first finishes and the result of the second is being returned to the first call of append but my (admittedly not great) understanding of how dart code is executed led me to believe that this is not possible. I've tried to simulate such circumstances with tests making hundreds of calls to append with different delays but I've been unable to reproduce such a result.
Note that I am using the timeout functionality of my FutureQueue class if that is helpful.
I am aware of alternative packages for making Futures run in a sequence (I'm not sure if any exist with exactly the same functionality) but I would like to fully understand the issue as it makes be doubt my understanding of how Dart is executed.
As far as I can tell, you must be running in unsound mode (sound null safety is not enabled), and you are having a null leak in through some non-null-safe library.
For instance, where does _fetcher come from? Is it delegating to an implementation provided by another library? Possibly that library is giving you a function which returns null in some scenario?
Here is a gist illustrating the situation https://gist.github.com/jakemac53/d95b9fd8aa55d03cf306b6c7b897132d.
I have a confusion in understanding Dart's async/await functionality. Most tutorials use the following example:
Future<String> createOrderMessage() async {
var order = await fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
// Imagine that this function is
// more complex and slow.
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
Future<void> main() async {
print('Fetching user order...');
print(await createOrderMessage());
}
I understand that the fetchUserOrder function returns a Future. Anyone who calls this function will immediately get a Future object as a result but this Future object may not have the real result value at that time. It will be filled up in "future".
Now, the createOrderMessage function calls: await fetchUserOrder(); This means, the control will wait till fetchUserOrder() returns a completed/non-empty Future. Am I right here? Is this what await does?
If that is correct, it means, the createOrderMessage function is essentially synchronous now because it won't return until the Future returned by fetchUserOrder is filled. Thus, the order variable in this function is initialized with a completed Future. Then why does this function need the "async" keyword in its function declaration? What does it signify?
Second confusion is if Dart is single threaded, when and how exactly is an async function executed? If I invoke multiple method calls that return Futures like this without await:
...
Future<A> fa = getA();
Future<B> fb = getB();
Future<C> fa = getC();
...
Will the functions getA, getB, and getC be executed sequentially in the background or they will be executed "in parallel" somehow?
Now, the createOrderMessage function calls: await fetchUserOrder(); This means, the control will wait till fetchUserOrder() returns a completed/non-empty Future. Am I right here? Is this what await does?
Yes. the await keyword means you get the completed T instead of Future<T> in return. So:
var a = fetchUserOrder(); // Type of a: Future<String>
var b = await fetchUserOrder(); // Type of b: String
When you use await, you tell the code to await the completion of the process and get the actual result. But without await, the stream is in the future and is in process until you await it somewhere else.
why does this function need the "async" keyword in its function declaration?
It's a rule to specify long-running functions. So when a function awaits for a long running process to complete, the function itself is also a long-running process. Why? Because it's waiting for another one to complete.
Therefore, you need to specify async for that as well. When another function is calling this one, it also knows this one might be long-running.
In short, every awaitable function needs to be in an async function.
Second confusion is if Dart is single threaded, when and how exactly is an async function executed?
You're mixing the concept of parallelism with asynchronous. An async process does not have to be done in multi thread.
Async processes can be one one at time, but in a different order in compare to synchronous code.
Take a look at this article which explains asynchronous in a single thread.
Now, the createOrderMessage function calls: await fetchUserOrder(); This means, the control will wait till fetchUserOrder() returns a completed/non-empty Future. Am I right here? Is this what await does?
If that is correct, it means, the createOrderMessage function is essentially synchronous now because it won't return until the Future returned by fetchUserOrder is filled. Thus, the order variable in this function is initialized with a completed Future.
await allows asynchronous functions to have the appearance of synchronous functions by allowing asynchronous code to be structured very similarly to synchronous code. In your example, it's syntactic sugar for registering a Future.then() callback and immediately returning:
Future<String> createOrderMessage() {
var future = fetchUserOrder().then((order) {
return 'Your order is: $order';
});
return future;
}
await defers execution of the rest of that function; it does not wait and block execution of your entire thread. Your program will continue running its event loop, possibly allowing other asynchronous operations to make progress.
Will the functions getA, getB, and getC be executed sequentially in the background or they will be executed "in parallel" somehow?
As I mentioned in comments, it depends on how getA, getB, and getC are implemented. They could run in parallel, such as if they run in separate Dart isolates or in separate threads in the Dart VM/runtime.
I have a setMethodCallHandler which runs a callback from my Java code, and I want it to set a Future containing the result. Something like
Future<String> fun() async {
return setMethodCallHandler((MethodCall call) {
return () async {return call.arguments["arg"];}();
});
}
What I want to be able to do is if call.argument will return "abc",
var a = await fun();
a will be equal to "abc"
The only information I found was the documentation on setMethodCallHandler:
If the future returned by the handler completes with a result, that value is sent back to the platform plugin caller wrapped in a success envelope as defined by the codec of this channel. If the future completes with a PlatformException, the fields of that exception will be used to populate an error envelope which is sent back instead.
But I don't understand how can I get the "platform plugin caller wrapped in a success envelope as defined by the codec of this channel"?
I'm guessing, but correct me if I'm wrong, that setMethodCallHandler does not return a value, it only sets up a function to be called later.
So, you can't use the return value of the setMethodCallHandler for anything.
That effectively means that you have an "event" of a kind, one that you want to "convert" into a future completion. To do that, you use a Completer to create and later complete a Future.
Future<String> fun() {
var completer = new Completer<String>();
setMethodCallHandler((MethodCall call) {
completer.complete(call.arguments["arg"]);
}
return completer.future;
}
Using async functions works when your events all come from futures or streams, but when you get other kinds of events (like port events, I/O callbacks or timers) and you want to map that back to future/stream events, you use either a Completer to complete a future or a StreamController to add events to a stream.
I want to execute same future function with different values. The order is not important. But I want to execute some functions after the above future function. My idea is
addrMapList.forEach((addrMap){ //length is 3
exeQuery(sql).then((result){
print(result);
});
});
print('All finished');
// other actions
Future exeQuery(String sql){
var c=new Completer();
Random rnd=new Random();
c.complete(rnd.nextInt(100));
return c.future;
}
But the result is
All finished
72
90
74
But I need a result like
72
90
74
All finished
How can this implement in dart.. Please help.
Here is modified version of your sample to work as you expected it to.
First of all, you should understand how asynchronous code works, and why it was not in your case:
When you write constructions like <some future>.then( (){...} ); you are not immediately running code defined inside .then( ). You just defining a callback, to be called later. So, in your code, you're defined 3 callbacks, and then, immediately, printed "All finished", at the time when no of your futures even started to work. At this moment they are just sitting in dart's event loop and waiting for a chance to be executed. And they will get that chance only when you finish execution of current code, and not a moment earlier, because Isolate is run as a single thread.
I used Future.wait() to wait for multiple futures because you said order is not important. This is more efficient then waiting Futures one by one. But if order is important, you have to use Future.forEach(), it will not start execution of second Future until first one is completed.
One more thing in your code is that your function returning a Future is actually synchronous, because it always returns already completed Future. This is also changed in dartpad sample above to better visualize how asynchronous code works.
forEach can't be used this way. Use await for instead (the enclosing function needs to be async)
Future someFunc() async {
await for (var addrMap in addrMapList) {
var result = await exeQuery(sql);
print(result);
}
// other action
}