I'm writing a program that needs to communicate with my bluetooth module.
I have singleton BluetoothController that has a future function which is supposed to send request to bluetooth and listen for and return a response, which comes through a broadcast stream. Now, the problem I'm having is that when I call the function from UI (gets called by a FutureBuilder) nothing happens, or rather FutureBuilder snapshot.hasData is false, so basically the future is waiting infinitely to finish. What I really don't understand is that the program seems to stop at return line of the BluetoothController function. And the even weirder thing is that if it's called again (via refresh for FutureBuilder) it works and prints 'done' when the second call to function goes inside 'await for' (after 'sendMessageToBluetooth', but before 'print(configData.toString())'). I was hoping someone can make sense of this because I haven't a clue what's going on here.
FutureBuilder future function in my main page. Gets called on page load (and doesn't work), and every 10 seconds (for testing):
Future<bool> getConfigData() async {
ConfigData? configData = await bluetoothController.getConfigData();
print('done'); // doesn't execute
...
}
Bluetooth controller (get's called from above code):
Future<ConfigData?> getConfigData() async {
var stream = listenForResponses(); // stream which yields strings when they arrive from bluetooth
sendMessageToBluetooth(getConfigDataRequest); // sends request to bluetooth module
await for (String data in stream) {
// 'done' from the other function gets printed around the time the second call to this function gets to this line
ConfigData configData = ConfigData.fromJson(jsonDecode(data)); // decodes json data
print(configData.toString()); // prints correct data, which means stream is yielding data
return configData; // this is where the program stops
}
return null; // linter wants this here
}
My understanding of what's happening is that the first call to the function returns a value only after the function is called again.
It's worth mentioning that using configData = await stream.first
in the function seems to block it at that line, it doesn't work, even though I'm sure the stream yielded a value.
CodePudding user response:
Edit 2
- The
await for
is a special syntax in dart, which is similar to astream
'slisten((e) {})
method. But there is a critical difference. theawait for
loop will only exit when thedone
event is encountered in thestream
, or when the last element is encountered in thestream
Hence, I would recommend that you remove thisawait for
and instead uselisten
on the stream, and then cancel the subscription when you receive the first element.
void getConfigData(void Function(ConfigData configData) onConfigData) {
var stream = listenForResponses(); // stream which yields strings when they arrive from bluetooth
sendMessageToBluetooth(getConfigDataRequest); // sends request to bluetooth module
// the stream subscription which we will cancel once we receive data,
// so that more events do not occur which might make your UI refresh
// when not intended (i.e. introduce bugs)
StreamSubscription? subscription;
// Assign subscription its value
subscription = stream.listen((data) {
// notify your listener that you have received your data.
onConfigData(ConfigData.fromJson(jsonDecode(data)));
// cancel the subscription once you receive your data.
subscription!.cancel();
});
}
- Now you can use this as follows:
void getConfigData() {
// You don't need to `await` here.
ConfigData? configData = bluetoothController.getConfigData(
(configData) => onConfigData(configData),
);
// Also, you can't return bools here, save a `bool` variable with a default value and update that in the `onConfigData` below
}
void onConfigData(ConfigData configData) {
print('done'); // this will execute now
// You should do your work here after receiving the data.
}
- To see an example which shows the difference of
listen
andawait for
, visit dartpad.dev
Edit 1:
- Can you try this code?
Future<ConfigData?> getConfigData() async {
var stream = listenForResponses(); // stream which yields strings when they arrive from bluetooth
sendMessageToBluetooth(getConfigDataRequest); // sends request to bluetooth module
ConfigData? configData;
await for (String data in stream) {
// 'done' from the other function gets printed around the time the second call to this function gets to this line
configData = ConfigData.fromJson(jsonDecode(data)); // We save this in a variable outside of this for loop's scope
print(configData.toString()); // should print what you wanted.
break; // We do not return, but break out of this for loop
}
return configData; // we return the data which is stored in configData by for loop.
}
Original
Here, if you return ConfigData
from inside the for
loop, the execution will stop as you have returned a value from the function getConfigData()
. Hence, it is best if you store the ConfigData
in a List<ConfigData>
instead of returning it as soon as the first element is received. This will let you get all the responses first, and then return a value. You can choose which value you want.