Can Dart Streams emit a value if the stream is not done within a duration? - flutter

I am working on a Flutter app using blocs to control the state of the view. I want to call and external API and if it responds quickly, show the results right away by yielding the result state. However, if the call takes more than, say, 5 seconds, I would like to yield a state indicating that the response is taking a while while still waiting for the API to return. How can I do this with Dart Streams, either natively or with RxDart?

This can be accomplished using Stream.timeout. Thanks #pskink!
Stream<String> delayedCall() async* {
yield 'Waiting';
final apiCall = Future.delayed(Duration(seconds: 5)).then((_) => 'Complete');
yield* Stream.fromFuture(apiCall).timeout(
Duration(seconds: 3),
onTimeout: (eventSink) => eventSink.add('Still waiting'),
);
}
void main() {
final stream = delayedCall();
stream.listen(print);
}

All the Futureshave a delayed timeout that you can use for example
try{
var timer = Timer(Duration(seconds: 3), () {
yield SlowAPI();
});
YourFeature.whenComplete((){
timer.cancel();
});
yield Success();
}catch(e){
yield Error();
}

Related

How to update events from SignalR in widget at the same time as event happens?

How to poll the event if new data appeared in the event? I am using SinglR package to get data from websocket. It has way to wotk with events from websockets, but my problem is it has the new event inside List, but dosen't automatically shows it in widget until I reload page manually. I am also using streams to get the pile of data to update by itselfs and at the same time as data appeares in event.
I found only one way: it is add Stream.pireodic, but image which comes from event is flickering, but I don't need that behaviour. How can I solve this issue?
The main issue: the widget is not instantly updated and does not show data from the event
P.S Nothing except SignalR library dosen't work with websocket in my case
List wanted = [];
Future<List?> fetchWantedFixation() async {
final httpConnectionOptions = HttpConnectionOptions(
accessTokenFactory: () => SharedPreferenceService().loginWithToken(),
skipNegotiation: true,
transport: HttpTransportType.WebSockets);
final hubConnection = HubConnectionBuilder()
.withUrl(
'http://websockets/sockets',
options: httpConnectionOptions,
)
.build();
await hubConnection.start();
String? wantedFixation;
int? index;
hubConnection.on('Wanted', (arguments) {
Future.delayed(const Duration(seconds: 7))
.then((value) => alarmplayer.StopAlarm());
alarmplayer.Alarm(url: 'assets/wanted.mp3', volume: 0.25);
wanted = arguments as List;
});
hubConnection.onclose(({error}) {
throw Exception(error);
});
print(wanted);
return wanted;
}
// it makes image flickering, but the event are updating
Stream<List> getEvent() async* {
yield* Stream.periodic(Duration(seconds: 5), (_) {
return wanted;
}).asyncMap((event) async => event);
}

Can't yield in forEachAsync inside Stream in dart/flutter

I have a forEachAsync inside an async* Stream and can't yield.
Stream<ProjectState> _mapProjectSelectedEventToState(ProjectSelected event) async* {
try {
yield ProjectLoading(
message: 'Fetching database',
fetchedCount: 0,
totalCount: 1,
);
await forEachAsync(fileModels, (FileEntity fileModel) async {
await downloader.download(filename: fileModel.hashName);
_totalMediaFilesFetched++;
//// ERROR - THIS DOES NOT WORK ////
yield (ProjectLoadingTick(
_totalMediaFiles,
_totalMediaFilesFetched,
));
}, maxTasks: 5);
} catch (error, stacktrace) {
yield ProjectFailure(error: error);
}
}
I've tried other means by dispatching the message and converting it to a state but it doesn't work as well. It seems like the whole app is blocked by this await forEachAsync.
I'm using the bloc pattern which reacts to the emited ProjectStates based on the current ProjectSelected event
Your attempt doesn't work because you're using yield in a callback, not in the function that's returning a Stream. That is, you're attempting the equivalent of:
Stream<ProjectState> _mapProjectSelectedEventToState(ProjectSelected event) async* {
...
await forEachAsync(fileModels, helperFunction);
...
}
Future helperFunction(FileEntity fileModel) async {
...
yield ProjectLoadingTick(...);
}
which doesn't make sense.
Since care about forEachAsync's ability to set a maximum limit to the number of outstanding asynchronous operations,
you might be better off using a StreamController that you can manually add events to:
var controller = StreamController<ProjectState>();
// Note that this is not `await`ed.
forEachAsync(fileModels, (FileEntity fileModel) async {
await downloader.download(filename: fileModel.hashName);
_totalMediaFilesFetched++;
controller.add(ProjectLoadingTick(
_totalMediaFiles,
_totalMediaFilesFetched,
));
},
maxTasks: 5);
yield* controller.stream;

How can I cancel a 'await for' in a flutter / dart bloc

I am using a stream to read out location data in a bloc. I have a start and a stop event. In the stop method, I cancel the stream subscription. When I use listen to a stream to yield the state the inside where the yield statement is never gets called.
Stream<LocationState> _start() async* {
_locationSubscription = location.onLocationChanged.listen(
(location) async* {
if (location.isNotNull) {
yield LocationState.sendData(location: updateLocation(location));
}
},
);
//send one initial update to change state
yield LocationState.sendData(
location: updateLocation(await Location().getLocation()));
}
Stream<LocationState> _stop() async {
await _locationSubscription?.cancel();
_locationSubscription = null;
yield LocationState.stoped();
}
When I replace the listen to await for I don't see any way
to stop this from yielding events because the subscription handle is gone.
Any ideas? Any explanations?
Stream<LocationState> _start() async* {
await for (LocationData location in location.onLocationChanged) {
if (location.isNotNull) {
yield LocationState.sendData(location: updateLocation(location));
}
}
//send one initial update to change state
yield LocationState.sendData(
location: updateLocation(await Location().getLocation()));
}
The problem is that I did not understand the behavior of yield completely.
Also, the dart framework has some shortcomings.
The problem was discussed in detail with the dart makers, here.
https://github.com/dart-lang/sdk/issues/42717
and here
https://github.com/felangel/bloc/issues/1472

Why does this test using FakeAsync just hang on the "await" even though the future has completed?

I'm trying to write a test using FakeAsync but it seems to hang on my awaits. Here's a stripped down example:
test('danny', () async {
await FakeAsync().run((FakeAsync async) async {
print('1');
final a = Future<bool>.delayed(const Duration(seconds: 5))
.then((_) => print('Delayed future completed!'))
.then((_) => true);
print('2');
async.elapse(const Duration(seconds: 30));
// Tried all this too...
// async.flushMicrotasks();
// async.flushTimers();
// async.elapse(const Duration(seconds: 30));
// async.flushMicrotasks();
// async.flushTimers();
// async.elapseBlocking(const Duration(seconds: 30));
print('3');
await a;
print('4');
expect(1, 2);
});
});
This code outputs:
1
2
Delayed future completed!
3
// hangs and never prints '4'
The async.elapse call is allowing the future to be completed, but it still hangs on await a. Why?
This seems to occur because although the Future is completed, the await call requires the microtask queue to be processed in order to continue (but it can't, since nobody is calling async.elapse after the await).
As a workaround, contiually pumping the microstask queue while the function is running seems to work - for example calling this function in place of FakeAsync.run:
/// Runs a callback using FakeAsync.run while continually pumping the
/// microtask queue. This avoids a deadlock when tests `await` a Future
/// which queues a microtask that will not be processed unless the queue
/// is flushed.
Future<T> runFakeAsync<T>(Future<T> Function(FakeAsync time) f) async {
return FakeAsync().run((FakeAsync time) async {
bool pump = true;
final Future<T> future = f(time).whenComplete(() => pump = false);
while (pump) {
time.flushMicrotasks();
}
return future;
}) as Future<T>;
}
It seems that await does not work inside fakeAsync, so you are stuck with Future.then() and friends.
However you can very easily wait for all Futures to complete by calling time.elapse()and time.flushMicrotasks().
test('Completion', () {
fakeAsync((FakeAsync time) {
final future = Future(() => 42);
time.elapse(Duration.zero);
expect(future, completion(equals(42)));
time.flushMicrotasks();
});
});

How to call async functions in Stream.periodic

Is it possible to call an async function inside dart:Stream.periodic function?
I tried to wrap my async function but it is not working, please see code below.
Stream.periodic(Duration(seconds: _pollingInterval), _checkConnectivity)
String _checkConnectivity(int x) async {
return await _connectionRepository.checkConnection();
}
Use asyncMap:
Stream<String> checkConnectionStream() async* {
yield* Stream.periodic(Duration(seconds: _pollingInterval), (_) {
return _connectionRepository.checkConnection();
}).asyncMap((event) async => await event);
}
I'm not too familiar with dart streams yet, but you should be able to simulate what you're trying to achieve like this:
final controller = StreamController<String>();
Timer timer;
controller.onListen = () {
timer = Timer.periodic(
_pollingInterval,
(timer) => _connectionRepository.checkConnection().then((data){
if(!controller.isClosed){
controller.add(data);
}
}),
);
};
controller.onCancel = () {
timer?.cancel();
}
return controller.stream;
The stream does not support pause and continue, though. If you want that you'd need to override the corresponding callbacks on the controller and start/stop the timer there.
Also, depending on the timing of checkConnection this can result in events in the stream being very different to _pollingInterval.