Home > Back-end >  Dart Future stops executing at return
Dart Future stops executing at return

Time:08-22

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 a stream's listen((e) {}) method. But there is a critical difference. the await for loop will only exit when the done event is encountered in the stream, or when the last element is encountered in the stream Hence, I would recommend that you remove this await for and instead use listen 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 and await 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.

  • Related