Await on stream to pulse x time before continuing - dart

Simple dart problem, got a stream
Stream<Event>
I subscribe to in a unit test. Would like something like this:
stream.listen(listener)
await listenerBeenNotified5Times
expect(result,expectation)
I know expectAsync can be used to make sure the notification happens 5 times, but I want to wait to pause the execution until 5 events have been streamed.

var s = stream.take(5);
var subscription = s.listen(listener);
await susciption.asFuture;
or
await stream.take(5).toList();

Related

How does one (and should one?) cancel a stream from an await-for loop in Dart?

I have a question about Dart streams, as follows. When I use a Stream via its listen() method, I can assign the result returned by listen to a variable and cancel the Subscription when I'm done (such as a dispose() method). How should I go about canceling a Stream listened by an await-for loop?
Just to clarify, I am not looking to change the flow of execution (as in, to cancel the Stream so that the code after the await-for runs), but to prevent memory leaks when I don't need the Stream anymore.
The one and only way to cancel the subscription created by an await for is to exit the loop.
The moment you leave the loop using a control flow operation like return, break, continue, throw or rethrow (or a yield operation in an async* function where the listener on the stream has cancelled), the cancel method on the underlying subscription is automatically cancelled.
If the loop terminates itself, then it is because the stream is already done, so there is nothing to worry about.
If you want to keep computing inside the loop for a long time, and then exit the loop, then I recommend restructuring the code to do the computation outside of the loop instead. That is:
await for (var event in stream) {
if (event.isTheOne) {
await longComputation(event);
break;
}
}
will keep the stream alive and paused until longComputation completes.
instead I'd do something like:
var theOne = null;
await for (var event in stream) {
if (event.isTheOne) {
theOne = event;
break;
}
}
if (theOne != null) await longComputation(theOne);
or something similar.
I think How should I go about canceling a Stream is a bit misspelled. In case of listen method you have properly written cancel the Subscription, because you cancel subscription, not a steam itself.
listen() is non-blocking, it creates a subscription, registers a callback and then continues to execute the next code in the current block of code. The await for construct is blocking, it does not create a subscription to the stream. The execution of code will not go beyond the await for scope until the stream is closed. It is described with examples here. So per my understanding you don't need to worry about memory leaks in case of await for.

Flutter Tests: Waiting for a certain duration

I'm trying to wait for some time after I tested tapping my button to then check the result with expect. I'm using Future.delayed for that. But that doesn't work for me. I'm getting a time out error.
TimeoutException after 0:00:05.000000: Test timed out after 5 seconds.
This is the code I use:
... // other tests
await tester.tap(find.widgetWithText(GestureDetector, "ref size"));
await new Future.delayed(new Duration(milliseconds: 50));
expect(testContainerState.childWidth, 50.0);
Does any one have an idea why this (imo) strange behavior occurs?
So, to start out with a simpler answer the correct way to wait for a period of time in a flutter test is using tester.pump.
await tester.pump(new Duration(milliseconds: 50));
The longer answer to why this happens has to do with the flutter testing environment. To make sure tests are reliable, even in the face of time-varying animations the environment mocks as much of the async behavior as possible, using utilities such as FakeAsync from package:quiver.
If you want to waiting data for Ex can you use this code :
await tester.runAsync(() async {
// test code here
});
tester is instance of WidgetTester

Syncronous waiting for a Future or a Stream to complete in Dart

I'm playing with a tiny web server and I'm implementing one version using the async package, and one synchronous version executing each request in a separate isolate. I would like to simply pipe a file stream to the HttpResponse, but I can't do that synchronously. And I can't find a way to wait for neither the Stream nor a Future synchronously. I'm now using a RandomAccessFile instead which works, but it becomes messier.
One solution would be to execute a periodical timer to check if the future is completed (by setting a boolean or similar), but that is most definitely not something I want to use.
Is there a way to wait synchronously for a Future and a Stream? If not, why?
For future visitors coming here simply wanting to perform some task after a Future or Stream completes, use await and await for inside an async method.
Future
final myInt = await getFutureInt();
Stream
int mySum = 0;
await for (int someInt in myIntStream) {
mySum += someInt;
}
Note
This may be technically different than performing a synchronous task, but it achieves the goal of completing one task before doing another one.
AFAIK there isn't a way to wait synchronously for a Future or a Stream. Why? Because these are asynchronous pretty much definitionally, and as you are discovering, the APIs are designed with asynchronous behavior in mind.
There are a couple of Future constructors, Future.value() and Future.sync(), that execute immediately, but I don't think these are probably what you have in mind.

Launching multiple async futures in response to events

I would like to launch a fairly expensive operation in response to a user clicking on a canvas element.
mouseDown(MouseEvent e) {
print("entering event handler");
var future = new Future<int>(expensiveFunction);
future.then((int value) => redrawCanvas(value);
print("done event handler");
}
expensiveFunction() {
for(int i = 0; i < 1000000000; i++){
//do something insane here
}
}
redrawCanvas(int value) {
//do stuff here
print("redrawing canvas");
}
My understanding of M4 Dart, is that this future constructor should launch "expensiveFunction" asynchronously, aka on a different thread from the main one. And it does appear this way, as "done event handler" is immediately printed into my output window in the IDE, and then some time later "redrawing canvas" is printed. However, if I click on the element again nothing happens until my "expensiveFunction" is done running from the previous click.
How do I use futures to simply launch an compute intensive function on new thread such that I can have multiple of them queued up in response to multiple clicks, even if the first future is not complete yet?
Thanks.
As mentioned in a different answer, Futures are just a "placeholder for a value that is made available in the future". They don't necessarily imply concurrency.
Dart has a concept of isolates for concurrency. You can spawn an isolate to run some code in a parallel thread or process.
dart2js can compile isolates into Web Workers. A Web Worker can run in a separate thread.
Try something like this:
import 'dart:isolate';
expensiveOperation(SendPort replyTo) {
var result = doExpensiveThing(msg);
replyTo.send(result);
}
main() async {
var receive = new ReceivePort();
var isolate = await Isolate.spawn(expensiveOperation, receive.sendPort);
var result = await receive.first;
print(result);
}
(I haven't tested the above, but something like it should work.)
Event Loop & Event Queue
You should note that Futures are not threads. They do not run concurrently, and in fact, Dart is single-threaded. All Dart code runs in an event loop.
The event loop is a loop that runs as long as the current Dart isolate is alive. When you call main() to start a Dart application, the isolate is created, and it is no longer alive after the main method is completed and all items on the event queue are completed as well.
The event queue is the set of all functions that still need to finish executing. Because Dart is single threaded, all of these functions need to run one at a time. So when one item in the event queue is completed, another one begins. The exact timing and scheduling of the event queue is something that's way more complicated than I can explain myself.
Therefore, asynchronous processing is important to prevent the single thread from being blocked by some long running execution. In a UI, a long process can cause visual jankiness and hinder your app.
Futures
Futures represent a value that will be available sometime in the Future, hence the name. When a Future is created, it is returned immediately, and execution continues.
The callback associated with that Future (in your case, expensiveFunction) is "started" by being added to the event queue. When you return from the current isolate, the callback runs and as soon as it can, the code after then.
Streams
Because your Futures are by definition asynchronous, and you don't know when they return, you want to queue up your callbacks so that they remain in order.
A Stream is an object that emits events that can be subscribed to. When you write canvasElement.onClick.listen(...) you are asking for the onClick Stream of MouseEvents, which you then subscribe to with listen.
You can use Streams to queue up events and register a callback on those events to run the code you'd like.
What to Write
main() {
// Used to add events to a stream.
var controller = new StreamController<Future>();
// Pause when we get an event so that we take one value at a time.
var subscription = controller.stream.listen(
(_) => subscription.pause());
var canvas = new CanvasElement();
canvas.onClick.listen((MouseEvent e) {
print("entering event handler");
var future = new Future<int>(expensiveFunction);
// Resume subscription after our callback is called.
controller.add(future.then(redrawCanvas).then(subscription.resume()));
print("done event handler");
});
}
expensiveFunction() {
for(int i = 0; i < 1000000000; i++){
//do something insane here
}
}
redrawCanvas(int value) {
//do stuff here
print("redrawing canvas");
}
Here we are queuing up our redrawCanvas callbacks by pausing after each mouse click, and then resuming after redrawCanvas has been called.
More Information
See also this great answer to a similar question.
A great place to start reading about Dart's asynchrony is the first part of this article about the dart:io library and this article about the dart:async library.
For more information about Futures, see this article about Futures.
For Streams information, see this article about adding to Streams and this article about creating Streams.

ytplayer api event when reaching a position in a video?

Is there a way to cause an event when a video reaches a specific time? I want to get to a callback function at the time when the video has reached to a certain time, and the time it takes for the video to reach that time is unpredictable, since the user can skip part of the video, or buffering might take some time before the video resumes, or something like that, so simply setting a timed event wont work because the video might reach specific time earlier.
I can query the time of the video, but what I want is to get a callback when the video has reached a certain time. Is there a way to do this?
I'm not going to write the full code, but you should set up an interval, like this:
var time = 70; // Time in seconds, e.g. this one is one minute and 10 seconds
var reached = false;
var interval = setInterval(function(){
if(player.getCurrentTime() >= time && !reached) {
clearInterval(interval);
reached = true;
timeReached();
}
},1000);
function timeReached() {
// Do what you have to
}
You can use this Javascript wrapper for the YouTube player API.
The API provides very simple event handling. E.g:
youtubePlayer.at('5000', function() {
alert("You're five seconds into this Youtube clip");
});
use player.getCurrentTime()!
https://developers.google.com/youtube/iframe_api_reference#Playback_status

Resources