Dart: Wait all StreamSubscription async onData event functions to complete - flutter

I have this specific code:
Stream<MimeMultipart> mm =
MimeMultipartTransformer(boundary).bind(Stream.fromIterable(l));
// Completer. So we can wait for the two response results.
Completer<dynamic> allComplete = Completer<dynamic>();
List<Future> myAsyncs = [];
myAsyncs.add(allComplete.future);
int listenCalledCount = 0;
mm.listen((event) async {
// this listen event executes multiple times...
Completer<dynamic> listenEventCompleted = Completer<dynamic>();
await Future.delayed(const Duration(seconds: 10));
print("Listening!!");
myAsyncs.add(listenEventCompleted.future);
listenCalledCount++; // How many times onData is called..
}, onDone: () => allComplete.complete(true));
await Future.wait(myAsyncs);
// I want all listen function called to be done first before continuing to the code below..
print(listenCalledCount);
print("All Done!");
My expected results are:
flutter: 3
flutter: All Done!
But what currently happening is:
flutter: 0
flutter: All Done!
What it means is, it doesn't wait for the StreamSubscription OnData event functions which is executed more than once to complete. I want all the OnData event functions to complete before proceeding. Is this possible?

Related

Flutter - Waiting for an asynchronous function call return from multiple synchronous function calls

I have an async function which is called multiple times synchoronusly.
List response = await Future.wait([future, future])
Inside, it popups a form and waiting for it to be submitted or cancelled.
var val = await Navigator.push(
context,
MaterialPageRoute(builder : (context) => const TheForm())
);
The first served Future will popup the form first and waiting for the return. No problem with that. But I want the second Future to check first if the form is already popped up. If it is, it just waiting for it to conclude and receive the same returned value.
I'm aware that receiving same function return from two calls sounds crazy and impossible. I'm just looking for a way to hold the second Future call on and trigger to conclude it from somewhere else.
Kindly tell me what I was missing and I'll provide the required information.
I try to use ValueNotifier's. Unfortunately ValueNotifier.addListener() only accept a VoidCallback. As for now, this is my solution. Still looking for a better way to replace the loop.
Future future() async{
if(ison) await Future.doWhile(() async {
await Future.delayed(Duration(seconds: 1));
return ison;
});
else{
ison = true;
result = ... //Popup form
ison = false;
}
return await result;
}
It sounds like you want to coalesce multiple calls to an asynchronous operation. Make your asynchronous operation cache the Future it returns and make subsequent calls return that Future directly. For example:
Future<Result>? _pending;
Future<Result> foo() {
if (_pending != null) {
return _pending!;
}
Future<Result> doActualWork() async {
// Stuff goes here (such as showing a form).
}
return _pending = doActualWork();
}
Now, no matter how many times you do await foo();, doActualWork() will be executed at most once.
If you instead want to allow doActualWork() to be executed multiple times and just to coalesce concurrent calls, then make doActualWork set _pending = null; immediately before it returns.

How to use Timer and await together in Dart/Flutter?

I have a code like below:
Timer(Duration(seconds: 5),(){
print("This is printed after 5 seconds.");
});
print("This is printed when Timer ends");
How can I use "await" in this case? I want when Timer ends then run next code below Timer. I used Future.delayed(), it can do it but I can't skip the delay time in Future.delayed() like in Timer(). Because i want to skip the delay time if the condition is true. Because I want to skip the delay time if the condition is true. If I use Future.delayed(), it doesn't have cancel() method like Timer().
Please tell me the solution. Thanks
Try Future.delayed instead of Timer
await Future.delayed(Duration(seconds: 5),(){
print("This is printed after 5 seconds.");
});
print('This is printed when Timer ends');
Timer(Duration(seconds: 5),(){
print("This is printed after 5 seconds.");
printWhenTimerEnds();
});
void printWhenTimerEnds(){
print("This is printed when Timer ends");
}
When you wish to skip the timer just call timer cancel and the printWhenTimerEnds() method
There are several ways to achieve what you need. You can use Completer and Future.any like this:
import 'dart:async';
Completer<void> cancelable = Completer<void>();
// auxiliary function for convenience and better code reading
void cancel() => cancelable.complete();
Future.any(<Future<dynamic>>[
cancelable.future,
Future.delayed(Duration(seconds: 5)),
]).then((_) {
if (cancelable.isCompleted) {
print('This is print when timer has been canceled.');
} else {
print('This is printed after 5 seconds.');
}
});
// line to test cancel, comment to test timer completion
Future.delayed(Duration(seconds: 1)).then((_) => cancel());
Basically we are creating two futures one is a delayed and another one is cancelable future. And we are waiting for the first one that completes using Future.any.
The other option is to use CancelableOperation or CancelableCompleter.
For example:
import 'dart:async';
import 'package:async/async.dart';
Future.delayed(Duration(seconds: 1)).then((_) => cancel());
var cancelableDelay = CancelableOperation.fromFuture(
Future.delayed(Duration(seconds: 5)),
onCancel: () => print('This is print when timer has been canceled.'),
);
// line to test cancel, comment to test timer completion
Future.delayed(Duration(seconds: 1)).then((_) => cancelableDelay.cancel());
cancelableDelay.value.whenComplete(() {
print('This is printed after 5 seconds.');
});
Here we do practically the same thing as above but with already available classes. We are wrapping Future.delayed into CancelableOperation so we are able now to cancel that operation (Future.delayed in our case).
Another way you can wrap Timer into future with Completer and so on.

Futures aren't finishing in queue

I build a queue of futures to ensure that those futures get completed in a sequential order, because even if one async function is called before the other this won't ensure that they get executed in this order when pushed on the event loop (my assumption).
In the UI there is a button, every time it gets pressed the async computation is pushed onto this queue. When pressed in a way that the async computation is finished before the next button press there isn't a problem but when pressed fast multiple times the computation won't finish.
In the example below every computation after job 1 won't finish.
int i = 0;
class TouchlessAlarmScheduleQueue {
Future _future = Future(() {});
addJob(String alarmId, Future<void> Function() job) {
print('$i added');
int iCopy = i;
_future = _future.then((_) {
print("$iCopy done");
return job();
});
i++;
}
}
Console Output:
flutter: 0 added
flutter: 0 done
flutter: 1 added
flutter: 2 added
flutter: 1 done
[VERBOSE-2:profiler_metrics_ios.mm(203)] Error retrieving thread information: (os/kern) invalid argument
Not sure if this profiler output is related to the problem.
Worked just fine here:
You should probably only use async/await for clarity. I have done the same thing before. I use an improved version in oun production app: https://twitter.com/gazialankus/status/1364695537585381378

How to use dynamic interval time in Stream.periodic event in Flutter

I'm struggling to find out a way to emit stream periodically with dynamic interval time in Flutter. I'm not sure, is it really possible or not. One workaround may be canceling the old periodic stream and reinitialize it with a new time interval but my periodic stream with asyncMap doesn't have a cancel option. I could use stream.listen that has cancel method but I purposely need asyncMap to convert the Future event to stream. In this case, what I can do please suggest to me.
My code snippet -
int i = 0;
int getTimeDiffForPeriodicEvent() {
i++;
return (_timeDiffBetweenSensorCommands * commandList.length + 1) * i;
}
StreamBuilder(
stream: Stream.periodic(
Duration(seconds: maskBloc.getTimeDiffForPeriodicEvent()))
.asyncMap((_) async => maskBloc.getDataFromMask()),
builder: (context, snapshot) {
return Container();
},
);
This isn't possible with Stream.periodic, but you could perhaps create a class that can start a stream and sleep based on some mutable variable by using async* and yield:
class AdjustablePeriodStream {
Duration period;
AdjustablePeriodStream(this.period);
Stream<void> start() async* {
while (true) {
yield null;
print('Waiting for $period');
await Future.delayed(period);
}
}
}
This would allow changing the period fairly easily:
Future<void> main() async {
final ten = Duration(milliseconds: 10);
final twenty = Duration(milliseconds: 20);
final x = AdjustablePeriodStream(ten);
x.start().take(5).listen((_) {
print('event!');
x.period = (x.period == ten ? twenty : ten);
});
}
You can see the example output here:
https://dartpad.dev/6a9cb253fbf29d8adcf087c30347835c
event!
Waiting for 0:00:00.020000
event!
Waiting for 0:00:00.010000
event!
Waiting for 0:00:00.020000
event!
Waiting for 0:00:00.010000
event!
Waiting for 0:00:00.020000
It just swaps between waiting 10 and 20 milliseconds (presumably you have some other mechanism you want to use for this). You'd probably also want some way to cancel the stream (which would bail out of the while (true) loop) but I ommitted it here to keep the code short and specific.

(Flutter/Dart) Two async methods in initState not working

I have a function in my initState:
#override
void initState() {
_fetchListItems();
super.initState();
}
This function is very simple. It has two async await operations of sqflite, one of which waits for the other to complete in it:
_fetchListItems() async {
wait() async {
number = await db.getNumber(userId); }
await wait();
List rawFavouriteList = await db.getList(number);
setState((){
rawFavouriteList.forEach((item){
_favouriteList.add(Model.map(item));
}});
}
I have to wait for number to be fetched, only then I can fetch serialized List, which then I deserialize and populate then add to the List which is displayed in ListView.
The problem is, this is not working in initState. It's working fine when it's called through onPressed() of a button, but not in initState.
Points to Note:
1) There is no error being thrown
2) I have already tried more conservative alternatives by using await for even rawFavouriteListthrough a separate function like wait() before using setState() top populate the _favouriteList, even though it's working fine manually through buttons.
3) Everything works fine if I manually enter number value in the second database query and just remove the first query, i.e.
_fetchListItems() async {
List rawFavouriteList = await db.getList(42);
setState((){
rawFavouriteList.forEach((item){
_favouriteList.add(Model.map(item));
}});
}