Can a Future internally retry an http request if it fails in Flutter?

Viewed 843

I'm using the following code to successfully poll mysite for JSON data and return that data. If the request fails, then it successfully returns an error message as well return Result.error(title:"No connection",msg:"Status code not 200", errorcode:0);.

What I'd like to happen is have the app retry the request 3 times before it returns the error message.

Basically have the future call itself for some number of iterations.

I did try to create an external function that would get called from the outer catch which then would in turn call the getJSONfromTheSite function a second and then a third time, but the problem was that you would have a non-null return from the Future so the app wouldn't accept that approach

Is there another way of doing this?

          Future<Result> getJSONfromTheSite(String call) async {
            debugPrint('Network Attempt by getJSONfromTheSite');
            try {

              final response = await http.get(Uri.parse('http://www.thesite.com/'));

              if (response.statusCode == 200) {
                return Result<AppData>.success(AppData.fromRawJson(response.body));
              } else {
                //return Result.error("Error","Status code not 200", 1);
                return Result.error(title:"Error",msg:"Status code not 200", errorcode:1);
              }
            } catch (error) {
                return Result.error(title:"No connection",msg:"Status code not 200", errorcode:0);
            }
          }
2 Answers

The following extension method will take a factory for futures, create them and try them until the retry limit is reached:

extension Retry<T> on Future<T> Function() {
  Future<T> withRetries(int count) async {
    while(true) {
      try {
        final future = this();
        return await future;
      } 
      catch (e) {
        if(count > 0) {
          count--;
        }
        else {
          rethrow;
        }
      }
    }
  }
}

Assuming you have a rather plain dart method:

 Future<AppData> getJSONfromTheSite(String call) async {
      final response = await http.get(Uri.parse('http://www.thesite.com/'));

      if (response.statusCode == 200) {
        return AppData.fromRawJson(response.body);
      } else {
        throw Exception('Error');
      }
  }

You can now call it like this:

try {
  final result = (() => getJSONfromTheSite('call data')).withRetries(3);
  // succeeded at some point, "result" being the successful result
}
catch (e) {
  // failed 3 times, the last error is in "e"
}

If you don't have a plain method that either succeeds or throws an exception, you will have to adjust the retry method to know when something is an error. Maybe use one of the more functional packages that have an Either type so you can figure out whether a return value is an error.

Inside catch() you can count how many times have you retried, if counter is less than three you return the same getJSONfromTheSite() function, but with the summed counter. And if the connection keeps failing on try{} and the counter is greater than three it will then returns the error.

Future<Result> getJSONfromTheSite(String call, {counter = 0}) async {
            debugPrint('Network Attempt by getJSONfromTheSite');
            try {
              String? body = await tryGet();
              if (body != null) {
                return Result<AppData>.success(AppData.fromRawJson(response.body));
              } else {
                //return Result.error("Error","Status code not 200", 1);
                return Result.error(title:"Error",msg:"Status code not 200", errorcode:1);
              }
            } catch (error) {
                if(counter < 3) {  
                  counter += 1;
                  return getJSONfromTheSite(call, counter: counter);
                } else {
                  return Result.error(title:"No connection",msg:"Status code not 200", errorcode:0);
                }
            }
          }
Related