How to call async functions in Stream.periodic - flutter

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.

Related

Asynchronous method not running in proper order

I have these methods, for some reason fetchItems is being called first before initPosition, how come dart wont wait for it to finish and proceeds to the second method? I've added async/await but it still doesn't work. I've also checked my backend logs to confirm this. Am I doing something wrong?
Future<void> initPosition() async {
if (_latitude != null && _longitude != null) {
await Socket.updatePosition(
lat: 51,
lon: 17,);
}
}
Future<void> initMarkers() async {
await initPosition();
await Provider.of<Items>(context, listen: false)
.fetchItems();
}
void initMapState() async {
await getCurrentLocation().then((_) async {
await initMarkers();
setState(() {
_loaded = true;
});
});
}
#override
void initState() {
super.initState();
_location.enableBackgroundMode(enable: false);
WidgetsBinding.instance?.addPostFrameCallback((_) {
initMapState();
});
}
Future<void> fetchItems() async {
itemList = await repository.getItemList();
notifyListeners();
}
Working with multiple asynchronous functions inside Futures depends on whether one is finished or not, not every single one. For this, you can call the "whenComplete" method so you can assure that your future function have finished running. Like this:
For your initMarkers() function:
Future<void> initMarkers() async {
await initPosition().whenComplete((){
Provider.of<Items>(context, listen: false)
.fetchItems();
});
}
For your initMapState() function:
void initMapState() async {
await getCurrentLocation().whenComplete(() async {
await initMarkers().whenComplete((){
setState(() {
_loaded = true;
});
});
});
}
Keep in mind that, in your code, you are not working with the returning value of your getCurrentLocation() function, so instead of using the "then" method use the "whenComplete" method, assuring that you changed or returned your values with this function. Finally, for the initState(), make the function body with asynchronous:
#override
void initState() {
super.initState();
_location.enableBackgroundMode(enable: false);
WidgetsBinding.instance?.addPostFrameCallback((_) async {
initMapState();
});
}
This should work.

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

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();
}

Is there a way to get notified when a dart stream gets its first result?

I currently have an async function that does the following:
Initializes the stream
Call stream.listen() and provide a function to listen to the stream.
await for the stream to get its first result.
The following is some pseudo code of my function:
Future<void> initStream() async {
// initialize stream
var stream = getStream();
// listen
stream.listen((result) {
// do some stuff here
});
// await until first result
await stream.first; // gives warning
}
Unfortunately it seems that calling stream.first counts as listening to the stream, and streams are not allowed to be listened by multiple...listeners?
I tried a different approach by using await Future.doWhile()
Something like the following:
bool gotFirstResult = false;
Future<void> initStream() async {
var stream = getStream();
stream.listen((result) {
// do some stuff here
gotFirstResult = true;
});
await Future.doWhile(() => !gotFirstResult);
}
This didn't work for me, and I still don't know why. Future.doWhile() was successfully called, but then the function provided to stream.listen() was never called in this case.
Is there a way to wait for the first result of a stream?
(I'm sorry if I didn't describe my question well enough. I'll definitely add other details if needed.)
Thanks in advance!
One way is converting your stream to broadcast one:
var stream = getStream().asBroadcastStream();
stream.listen((result) {
// do some stuff here
});
await stream.first;
Another way, without creating new stream, is to use Completer. It allows you to return a Future which you can complete (send value) later. Caller will be able to await this Future as usual.
Simple example:
Future<int> getValueAsync() {
var completer = Completer<int>();
Future.delayed(Duration(seconds: 1))
.then((_) {
completer.complete(42);
});
return completer.future;
}
is equivalent of
Future<int> getValueAsync() async {
await Future.delayed(Duration(seconds: 1));
return 42;
}
In your case:
Future<void> initStream() {
var stream = getStream();
var firstValueReceived = Completer<void>();
stream.listen((val) {
if (!firstValueReceived.isCompleted) {
firstValueReceived.complete();
}
// do some stuff here
});
return firstValueReceived.future;
}

How to resolve a Future in flutter (dart)?

I want to write a functions which returns until an upload has been finished. If it is possible it would be good if I could also add a timeout.
waitForUpload() async {
uploader.result.listen((result) {
// return waitForUpload
}
}
I just don't find how to write this in dart. To make it more clear: In JS the code would look like this:
async waitForUpload() {
return new Promise((resolve) => {
uploader.result.listen((result) {
resolve();
});
});
}
Using a Completer would be more straightforward.
Future time(int time) async {
Completer c = new Completer();
new Timer(new Duration(seconds: time), (){
c.complete('done with time out');
});
return c.future;
}
Stream.single implements the behavior I want. Looking at implementation, you can see future._complete(result); is called inside the listen method which resolves the future.

is there any way to cancel a dart Future?

In a Dart UI, I have a button submit to launch a long async request. The submit handler returns a Future. Next, the button submit is replaced by a button cancel to allow the cancellation of the whole operation. In the cancel handler, I would like to cancel the long operation. How can I cancel the Future returned by the submit handler? I found no method to do that.
You can use CancelableOperation or CancelableCompleter to cancel a future. See below the 2 versions:
Solution 1: CancelableOperation (included in a test so you can try it yourself):
cancel a future
test("CancelableOperation with future", () async {
var cancellableOperation = CancelableOperation.fromFuture(
Future.value('future result'),
onCancel: () => {debugPrint('onCancel')},
);
// cancellableOperation.cancel(); // uncomment this to test cancellation
cancellableOperation.value.then((value) => {
debugPrint('then: $value'),
});
cancellableOperation.value.whenComplete(() => {
debugPrint('onDone'),
});
});
cancel a stream
test("CancelableOperation with stream", () async {
var cancellableOperation = CancelableOperation.fromFuture(
Future.value('future result'),
onCancel: () => {debugPrint('onCancel')},
);
// cancellableOperation.cancel(); // uncomment this to test cancellation
cancellableOperation.asStream().listen(
(value) => { debugPrint('value: $value') },
onDone: () => { debugPrint('onDone') },
);
});
Both above tests will output:
then: future result
onDone
Now if we uncomment the cancellableOperation.cancel(); then both above tests will output:
onCancel
Solution 2: CancelableCompleter (if you need more control)
test("CancelableCompleter is cancelled", () async {
CancelableCompleter completer = CancelableCompleter(onCancel: () {
print('onCancel');
});
// completer.operation.cancel(); // uncomment this to test cancellation
completer.complete(Future.value('future result'));
print('isCanceled: ${completer.isCanceled}');
print('isCompleted: ${completer.isCompleted}');
completer.operation.value.then((value) => {
print('then: $value'),
});
completer.operation.value.whenComplete(() => {
print('onDone'),
});
});
Output:
isCanceled: false
isCompleted: true
then: future result
onDone
Now if we uncomment the cancellableOperation.cancel(); we get output:
onCancel
isCanceled: true
isCompleted: true
Be aware that if you use await cancellableOperation.value or await completer.operation then the future will never return a result and it will await indefinitely if the operation was cancelled. This is because await cancellableOperation.value is the same as writing cancellableOperation.value.then(...) but then() will never be called if the operation was cancelled.
Remember to add async Dart package.
Code gist
How to cancel Future.delayed
A simple way is to use Timer instead :)
Timer _timer;
void _schedule() {
_timer = Timer(Duration(seconds: 2), () {
print('Do something after delay');
});
}
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
As far as I know, there isn't a way to cancel a Future. But there is a way to cancel a Stream subscription, and maybe that can help you.
Calling onSubmit on a button returns a StreamSubscription object. You can explicitly store that object and then call cancel() on it to cancel the stream subscription:
StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
// you code here
if (someCondition == true) {
subscription.cancel();
}
});
Later, as a response to some user action, perhaps, you can cancel the subscription:
For those, who are trying to achieve this in Flutter, here is the simple example for the same.
class MyPage extends StatelessWidget {
final CancelableCompleter<bool> _completer = CancelableCompleter(onCancel: () => false);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Future")),
body: Column(
children: <Widget>[
RaisedButton(
child: Text("Submit"),
onPressed: () async {
// it is true only if the future got completed
bool _isFutureCompleted = await _submit();
},
),
RaisedButton(child: Text("Cancel"), onPressed: _cancel),
],
),
);
}
Future<bool> _submit() async {
_completer.complete(Future.value(_solve()));
return _completer.operation.value;
}
// This is just a simple method that will finish the future in 5 seconds
Future<bool> _solve() async {
return await Future.delayed(Duration(seconds: 5), () => true);
}
void _cancel() async {
var value = await _completer.operation.cancel();
// if we stopped the future, we get false
assert(value == false);
}
}
One way I accomplished to 'cancel' a scheduled execution was using a Timer. In this case I was actually postponing it. :)
Timer _runJustOnceAtTheEnd;
void runMultipleTimes() {
_runJustOnceAtTheEnd?.cancel();
_runJustOnceAtTheEnd = null;
// do your processing
_runJustOnceAtTheEnd = Timer(Duration(seconds: 1), onceAtTheEndOfTheBatch);
}
void onceAtTheEndOfTheBatch() {
print("just once at the end of a batch!");
}
runMultipleTimes();
runMultipleTimes();
runMultipleTimes();
runMultipleTimes();
// will print 'just once at the end of a batch' one second after last execution
The runMultipleTimes() method will be called multiple times in sequence, but only after 1 second of a batch the onceAtTheEndOfTheBatch will be executed.
my 2 cents worth...
class CancelableFuture {
bool cancelled = false;
CancelableFuture(Duration duration, void Function() callback) {
Future<void>.delayed(duration, () {
if (!cancelled) {
callback();
}
});
}
void cancel() {
cancelled = true;
}
}
There is a CancelableOperation in the async package on pub.dev that you can use to do this now. This package is not to be confused with the built in dart core library dart:async, which doesn't have this class.
Change the future's task from 'do something' to 'do something unless it has been cancelled'. An obvious way to implement this would be to set a boolean flag and check it in the future's closure before embarking on processing, and perhaps at several points during the processing.
Also, this seems to be a bit of a hack, but setting the future's timeout to zero would appear to effectively cancel the future.
The following code helps to design the future function that timeouts and can be canceled manually.
import 'dart:async';
class API {
Completer<bool> _completer;
Timer _timer;
// This function returns 'true' only if timeout >= 5 and
// when cancelOperation() function is not called after this function call.
//
// Returns false otherwise
Future<bool> apiFunctionWithTimeout() async {
_completer = Completer<bool>();
// timeout > time taken to complete _timeConsumingOperation() (5 seconds)
const timeout = 6;
// timeout < time taken to complete _timeConsumingOperation() (5 seconds)
// const timeout = 4;
_timeConsumingOperation().then((response) {
if (_completer.isCompleted == false) {
_timer?.cancel();
_completer.complete(response);
}
});
_timer = Timer(Duration(seconds: timeout), () {
if (_completer.isCompleted == false) {
_completer.complete(false);
}
});
return _completer.future;
}
void cancelOperation() {
_timer?.cancel();
if (_completer.isCompleted == false) {
_completer.complete(false);
}
}
// this can be an HTTP call.
Future<bool> _timeConsumingOperation() async {
return await Future.delayed(Duration(seconds: 5), () => true);
}
}
void main() async {
API api = API();
api.apiFunctionWithTimeout().then((response) {
// prints 'true' if the function is not timed out or canceled, otherwise it prints false
print(response);
});
// manual cancellation. Uncomment the below line to cancel the operation.
//api.cancelOperation();
}
The return type can be changed from bool to your own data type. Completer object also should be changed accordingly.
A little class to unregister callbacks from future. This class will not prevent from execution, but can help when you need to switch to another future with the same type. Unfortunately I didn't test it, but:
class CancelableFuture<T> {
Function(Object) onErrorCallback;
Function(T) onSuccessCallback;
bool _wasCancelled = false;
CancelableFuture(Future<T> future,
{this.onSuccessCallback, this.onErrorCallback}) {
assert(onSuccessCallback != null || onErrorCallback != null);
future.then((value) {
if (!_wasCancelled && onSuccessCallback != null) {
onSuccessCallback(value);
}
}, onError: (e) {
if (!_wasCancelled && onErrorCallback != null) {
onErrorCallback(e);
}
});
}
cancel() {
_wasCancelled = true;
}
}
And here is example of usage. P.S. I use provider in my project:
_fetchPlannedLists() async {
if (_plannedListsResponse?.status != Status.LOADING) {
_plannedListsResponse = ApiResponse.loading();
notifyListeners();
}
_plannedListCancellable?.cancel();
_plannedListCancellable = CancelableFuture<List<PlannedList>>(
_plannedListRepository.fetchPlannedLists(),
onSuccessCallback: (plannedLists) {
_plannedListsResponse = ApiResponse.completed(plannedLists);
notifyListeners();
}, onErrorCallback: (e) {
print('Planned list provider error: $e');
_plannedListsResponse = ApiResponse.error(e);
notifyListeners();
});
}
You could use it in situations, when language changed, and request was made, you don't care about previous response and making another request!
In addition, I really was wondered that this feature didn't come from the box.
Here's a solution to cancel an awaitable delayed future
This solution is like an awaitable Timer or a cancelable Future.delayed: it's cancelable like a Timer AND awaitable like a Future.
It's base on a very simple class, CancelableCompleter, here's a demo:
import 'dart:async';
void main() async {
print('start');
// Create a completer that completes after 2 seconds…
final completer = CancelableCompleter.auto(Duration(seconds: 2));
// … but schedule the cancelation after 1 second
Future.delayed(Duration(seconds: 1), completer.cancel);
// We want to await the result
final result = await completer.future;
print(result ? 'completed' : 'canceled');
print('done');
// OUTPUT:
// start
// canceled
// done
}
Now the code of the class:
class CancelableCompleter {
CancelableCompleter.auto(Duration delay) : _completer = Completer() {
_timer = Timer(delay, _complete);
}
final Completer<bool> _completer;
late final Timer? _timer;
bool _isCompleted = false;
bool _isCanceled = false;
Future<bool> get future => _completer.future;
void cancel() {
if (!_isCompleted && !_isCanceled) {
_timer?.cancel();
_isCanceled = true;
_completer.complete(false);
}
}
void _complete() {
if (!_isCompleted && !_isCanceled) {
_isCompleted = true;
_completer.complete(true);
}
}
}
A running example with a more complete class is available in this DartPad.
You can use timeout() method
Create a dummy future:
Future<String?> _myFuture() async {
await Future.delayed(const Duration(seconds: 10));
return 'Future completed';
}
Setting a timeout of 3 seconds to stop early from 10sec:
_myFuture().timeout(
const Duration(seconds: 3),
onTimeout: () =>
'The process took too much time to finish. Please try again later',
);
and thats it you cancel your FUTURE.
there is no way unfortunately, take a look:
import 'dart:async';
import 'package:async/async.dart';
void main(List<String> args) async {
final object = SomeTimer();
await Future.delayed(Duration(seconds: 1));
object.dispose();
print('finish program');
}
class SomeTimer {
SomeTimer() {
init();
}
Future<void> init() async {
completer
.complete(Future.delayed(Duration(seconds: 10), () => someState = 1));
print('before wait');
await completer.operation.valueOrCancellation();
print('after wait');
if (completer.isCanceled) {
print('isCanceled');
return;
}
print('timer');
timer = Timer(Duration(seconds: 5), (() => print('finish timer')));
}
Timer? timer;
int _someState = 0;
set someState(int value) {
print('someState set to $value');
_someState = value;
}
CancelableCompleter completer = CancelableCompleter(onCancel: () {
print('onCancel');
});
void dispose() {
completer.operation.cancel();
timer?.cancel();
}
}
after ten seconds you will see someState set to 1 no matter what