flutterdartasynchronouspolling

How to write a polling function for polling an API until a URL is returned?


In Flutter, I am trying to write a function that periodically calls an API until a URL is returned, then passes that URL to another function. How the API works is that on the first call, it responds with acknowledgement that a request is made. Then, after a period of time, it generates a URL that can be clicked to export files.

However, my current error is that the function ends before the polling is finished and the URL is assigned to the exportURL variable I want to return to the other function.

The error I get is [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioException [unknown]: null, Error: Invalid argument(s): No host specified in URI as the URL is not passed correctly to my other function.

Below is my function to poll the API.

Future<String> _pollingForExport(String url, Options options) async{
    String exportUrl = '';
    Timer.periodic(const Duration(seconds: 3), (timer) async {
      //Call API with post method again
      final responseRequested = await dio.post(
        url,
        options: options,
      );
      print('In polling mechanism, response requested is ${responseRequested.data}');

      //If successful request and the response is a url String, cancel the timer and exit Polling
      //We know if the response is a link if it just contains a string, which is the url
      if(responseRequested.statusCode == 200 && responseRequested.data is String){
        print('Link should be found. response is ${responseRequested.data}');
        exportUrl = responseRequested.data as String;
        timer.cancel();
      }
      //Else if there is a bad request, tell user error and return function
      else if (responseRequested.statusCode != 200){
        print('Error when polling API, bad request made');
        timer.cancel();
        return ;  //Not sure if I return here, but seems right
      }
    });

    return exportUrl;
  }

Below is the code where the url is returned by the polling function. It is passed to dio.download to export the file.

String exportUrl = await _pollingForExport(url, options);
String downloadPath = (await getApplicationDocumentsDirectory()).path;
final responseExport = await dio.download(exportUrl, downloadPath + '.txt', options: options);

I have tried reading documentation on Flutter timer and async functions. I also tried not using a function and just sticking the timer where I need it, but that didn't work either.

Any help is appreciated, thank you!


Solution

  • the problem is that your function returns immediately, it does not "await" the timer.

    Future<String> _pollingForExport(String url, Options options) async{
        String exportUrl = '';
    //This is not awaited
    Timer.periodic(const Duration(seconds: 3), (timer) async {
          //Call API with post method again
          final responseRequested = await dio.post(
            url,
            options: options,
          );
          print('In polling mechanism, response requested is ${responseRequested.data}');
    
          //If successful request and the response is a url String, cancel the timer and exit Polling
          //We know if the response is a link if it just contains a string, which is the url
          if(responseRequested.statusCode == 200 && responseRequested.data is String){
            print('Link should be found. response is ${responseRequested.data}');
            exportUrl = responseRequested.data as String;
            timer.cancel();
          }
          //Else if there is a bad request, tell user error and return function
          else if (responseRequested.statusCode != 200){
            print('Error when polling API, bad request made');
            timer.cancel();
            return ;  //Not sure if I return here, but seems right
          }
        });
    
    //this returns immediately
        return exportUrl;
      }
    

    Maybe a completer would be better

    Future<String> _pollingForExport(String url, Options options) async{
      final exportUrlCompleter = Completer<String>();
    
    
    //This is not awaited
      Timer.periodic(const Duration(seconds: 3), (timer) async {
        //Call API with post method again
        final responseRequested = await dio.post(
          url,
          options: options,
        );
        print('In polling mechanism, response requested is ${responseRequested.data}');
    
        //If successful request and the response is a url String, cancel the timer and exit Polling
        //We know if the response is a link if it just contains a string, which is the url
        if(responseRequested.statusCode == 200 && responseRequested.data is String){
          print('Link should be found. response is ${responseRequested.data}');
          final exportUrl = responseRequested.data as String;
          timer.cancel();
          exportUrlCompleter.complete(exportUrl);
        }
        //Else if there is a bad request, tell user error and return function
        else if (responseRequested.statusCode != 200){
          print('Error when polling API, bad request made');
          exportUrlCompleter.completeError("Exception occurred");
          timer.cancel();
        }
      });
    
    //this returns immediately
      return exportUrlCompleter.future;
    }