Exit an infinite stream - flutter

I've written this piece of code in order to scan Bluetooth devices in my Flutter app with the help of flutter_blue package.
Stream<List<ScanResult>> scannedDevices(int durationSeconds) async* {
Stream<List<ScanResult>> stream = _flutterBlue.scanResults;
List<ScanResult> payload = List<ScanResult>.empty(growable: true);
_flutterBlue.startScan(timeout: Duration(seconds: durationSeconds));
await for (List<ScanResult> results in stream) {
payload.clear();
for (ScanResult result in results) {
payload.add(result);
}
yield payload;
}
The problem is that scanResults Stream is infitnite (see here) and because of that the await for loop runs foreverer, leaving my scannedDevices Stream running forever too.
Is there a way to break the await for loop after scanDuration has passed? Thanks

Simply add an timeout to the stream that you are listening.
Another option is to use the StreamTransformer instead of what you did with await for.
Stream<List<ScanResult>> scannedDevices(int durationSeconds) async* {
Stream<List<ScanResult>> stream = _flutterBlue.scanResults.timeout(Duration(seconds: durationSeconds));
List<ScanResult> payload = List<ScanResult>.empty(growable: true);
_flutterBlue.startScan(timeout: Duration(seconds: durationSeconds));
await for (List<ScanResult> results in stream) {
payload.clear();
for (ScanResult result in results) {
payload.add(result);
}
yield payload;
}
}

Yes, you can simply return null for breaking loop
Stream<List<ScanResult>> scannedDevices(int durationSeconds)
async* {
Stream<List<ScanResult>> stream = _flutterBlue.scanResults;
List<ScanResult> payload = List<ScanResult>.empty(growable:
true);
_flutterBlue.startScan(timeout: Duration(seconds:
durationSeconds));
await for (List<ScanResult> results in stream) {
payload.clear();
for (ScanResult result in results) {
payload.add(result);
}
return payload;
}

Related

Flutter - an async function returns before really finishing?

I have a function scanAndConnect() that should scan for BLE devices and connect to the device with the specified service ID. This function should be async and should return Future.
The problem is that scanAndConnect() prints 99999 and returns without waiting for flutterReactiveBle.statusStream.listen() to finish although I use await before it.
Future scanAndConnect(Uuid serviceId, Uuid charctId) async {
StreamSubscription<BleStatus>? bleStatusStreamSubscription;
StreamSubscription<DiscoveredDevice>? deviceStreamSubscription;
Stream<DiscoveredDevice> stream;
bleStatusStreamSubscription =
await flutterReactiveBle.statusStream.listen((bleStatus) async {
print("new listen ${bleStatus.toString()}");
if (bleStatus == BleStatus.ready) {
await bleStatusStreamSubscription!.cancel();
connectionStatus = BLEConnectionStatus.Connecting;
stream = await flutterReactiveBle.scanForDevices(
withServices: [serviceId],
scanMode: ScanMode.lowLatency,
);
}
});
print("9999999");
}
....
Future connectToDevice() async {
await ble.scanAndConnect(BLE_SERVICE_UUID, BLE_CHAR_UUID)
print("Statement after await in main");
setState(() {
loading = false;
print("Changing state to ${loading.toString()}");
});
}
This is the output I get in Xcode:
flutter: 9999999
flutter: Statement after await in main
flutter: Changing state to false
flutter: new listen BleStatus.unknown
flutter: new listen BleStatus.ready
How can I make scanAndConnect doesn't return before really finishing?
According to the documentation, FlutterReactiveBle.scanForDevices() returns a Stream, not a Future, so await will not work here. You can use
await for
listen()
await stream.first()
to wait for data from a Stream.

how to create a stream in flutter that return a bool in every second

i am making a app. And i want to check my server state every minite and give user information
about the server. How do i do it. is stream good for it. Can some provide me a code for that.
just follow this guide
suppose your bool return value function is
Future<bool> isGpsOn() async {
return await Geolocator().isLocationServiceEnabled();
}
and this is create stream from bool value
Stream futureToStream(fn, defaultValue, Duration duration) async* {
var result;
while (true) {
try {
result = await fn();
}
catch (error) {
result = defaultValue;
}
finally {
yield result;
}
await Future.delayed(duration);
}
}
final gpsStatusStream = futureToStream(isGpsOn, false, Duration(seconds: 5));
gpsStatusStream.listen((enabled) {
print(enabled ? 'enabled' : 'disabled');
});
Use asyncMap
Stream<String> checkConnectionStream() async* {
yield* Stream.periodic(Duration(seconds: 1), (_) {
return //your function
}).asyncMap((event) async => await 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;

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

Flutter: How to Queue multiple streams

I have stream like the below-mentioned code, here want to merge streams and wanted to call it one by one whenever it required.
main() {
Stream<String> stream = new Stream.fromFuture(getData());
stream.listen((data) {
print("DataReceived: "+data);
}, onDone: () {
print("Task Done");
});
}
Future<String> getData() async {
await Future.delayed(Duration(seconds: 5)); //Mock delay
print("Fetched Data");
return "This a test data";
}
onDone will be called after 5 seconds.
For it, StreamQueue can be used to combine multiple streams
Add Dependency:
async: ^2.4.1
Use StreamQueue:
void main() async {
Stream<String> stream1 = new Stream.fromFuture(getData(2));
Stream<String> stream2 = new Stream.fromFuture(getData(4));
Stream<String> stream3 = new Stream.fromFuture(getData(6));
var streams = StreamGroup.merge([stream1, stream2, stream3]);
var data = StreamQueue(streams);
var first = await data.next;
print(first);
var second = await data.next;
print(second);
var third = await data.next;
print(third);
var third1 = await data.next;
print(third1);
}
Future<String> getData(int duration) async {
await Future.delayed(Duration(seconds: duration)); //Mock delay
return "This a test data for duration $duration";
}
Output:
I/flutter ( 7950): This a test data for duration 2
I/flutter ( 7950): This a test data for duration 4
I/flutter ( 7950): This a test data for duration 6
data.next will provide you the first stream in Queue which is in our
case is stream1, here we can take the future value and use. stream.next gives next stream in the queue if nothing exists in queue & stream.next is performed then it will throw an exception.