简体   繁体   中英

Flutter Dio interceptor Error: Bad state: Future already completed

I have an interceptor to send jwt token and to use the refresh_token endpoint when the jwt expires. With an expired jwt I get

Error: Bad state: Future already completed

error, but the request is processed right anyway. In the console I see one successful response and one with 401 error afterward. How can I solve this issue?

custom_interceptor.dart

class CustomInterceptor extends DefaultInterceptor {
  ISecureStorage secureStorageService = ISecureStorage();

  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    LoginModel loginModel = await secureStorageService.readLoginModel();

    options.headers = {
      "Content-type": "application/json",
      "Authorization": "Bearer ${loginModel.access_token}"
    };
    return super.onRequest(options, handler);
  }

  @override
  void onError(err, handler) async {
    if (err.response?.statusCode == 401) {
      final Dio _dio = DioConfig().dio;
      LoginModel loginModel = await secureStorageService.readLoginModel();
      Uri uri = Uri.https(
          "$BASE_URL", "/refresh_token_url");
      try {
        await _dio.postUri(uri, data: {
          "refresh_token": loginModel.refresh_token,
          "grant_type": "refresh_token"
        }).then((value) async {
          if (value?.statusCode == 200) {

            await secureStorageService.deleteLoginModel();
            LoginModel newLoginData = LoginModel.fromJson(value.data);
            await secureStorageService.saveLoginModel(loginModel: newLoginData);
            
            err.requestOptions.headers["Authorization"] =
                "Bearer " + newLoginData.refresh_token;

            final opts = new Options(
                method: err.requestOptions.method,
                headers: err.requestOptions.headers);
            final cloneReq = await _dio.request(err.requestOptions.path,
                options: opts,
                data: err.requestOptions.data,
                queryParameters: err.requestOptions.queryParameters);
            return handler.resolve(cloneReq);
          }
          return err;
        });
        return super.onError(err, handler);
      } catch (e, st) {
        print("ERROR: " + e);
        print("STACK: " + st.toString());
        return super.onError(err, handler);
      }
    } else {
      return super.onError(err, handler);
    }
  }
}
class DefaultInterceptor extends Interceptor {
  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    print(
        'REQUEST[${options.method}] => PATH: ${options.path} | DATA => ${options.data} | JWT => ${options.headers}');
    return super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print(
        'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path} | DATA => ${response.data}');
    super.onResponse(response, handler);
    return;
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) async {
    print(
        'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path} | SENT_DATA => ${err.requestOptions.data} | RECEIVED_DATA => ${err.response?.data}');
    return super.onError(err, handler);
  }
}

dio_config.dart

class DioConfig {
    static DioConfig _singletonHttp;
    Dio _dio;

    get dio => _dio;

    factory DioConfig() {
        _singletonHttp ??= DioConfig._singleton();
        return _singletonHttp;
    }

    DioConfig._singleton() {
        _dio = Dio();
    }

    dispose() {
        _dio.close();
    }
}

i_secure_storage.dart

abstract class ISecureStorage {

    factory ISecureStorage() => getSecureStorage();

    Future<LoginModel> readLoginModel() async => LoginModel.empty;

    Future<bool> saveLoginModel({LoginModel loginModel}) async => false;

    Future<bool> deleteLoginModel() async => false;
}

web_secure_storage.dart

ISecureStorage getSecureStorage() => WebSecureStorageService();

class WebSecureStorageService implements ISecureStorage {

    final String _loginData = 'loginData';
    html.Storage webStorage = html.window.localStorage;

    @override
    Future<LoginModel> readLoginModel() async {
        return webStorage[_loginData] == null
                ? LoginModel.empty
                : LoginModel.fromJson(jsonDecode(webStorage[_loginData]));
    }

    @override
    Future<bool> saveLoginModel({ LoginModel loginModel}) async {
        webStorage[_loginData] = jsonEncode(loginModel);
        return true;
    }

    @override
    Future<bool> deleteLoginModel() async {
        webStorage.remove(_loginData);
        return true;
    }
}

mobile_secure_storage.dart

ISecureStorage getSecureStorage() => MobileSecureStorageService();

class MobileSecureStorageService implements ISecureStorage {
    final String _loginModel = 'loginModel';

    FlutterSecureStorage storage = const FlutterSecureStorage();

    @override
    Future<LoginModel> readLoginModel() async {
        try {
            dynamic _loginData = await storage.read(key: _loginModel);
            return _loginData == null ? LoginModel.empty : LoginModel.fromJson(jsonDecode(_loginData));
        } on PlatformException catch (ex) {
            throw PlatformException(code: ex.code, message: ex.message);
        }
    }

    @override
    Future<bool> saveLoginModel({LoginModel loginModel}) async {
        try {
            await storage.write(key: _loginModel, value: jsonEncode(loginModel));
            return true;
        } on PlatformException catch (ex) {
            throw PlatformException(code: ex.code, message: ex.message);
        }
    }

    @override
    Future<bool> deleteLoginModel() async {
        try {
            await storage.delete(key: _loginModel);
            return true;
        } on PlatformException catch (ex) {
            throw PlatformException(code: ex.code, message: ex.message);
        }
    }
}

EDIT:

the problem is in the first return super.onError(err, handler); It must be return null;

For anyone else having this issue and it is not solved by only downgrading dio: Downgrade dio to 4.0.4 AND remove connectTimeout from your BaseOptions .

You are using Dio for the requests. Version 4.0.6 of Dio which is the most recent version as of today has this known issue. Please refer to the same on GitHub here .

Solution

Downgrade your Dio package to the last stable version that was known to not have this issue until a new version is released.

In your pubspec.yaml.

dio: 4.0.4

Then get packages again.

> flutter pub get
class InterceptorsWrapper extends QueuedInterceptorsWrapper {
@override
void onRequest(RequestOptions options,RequestInterceptorHandler handler){
 log('send request:${options.baseUrl}${options.path}');

 final accessToken = Storage.instance.box.read("accessToken");

 options.headers['Authorization'] = 'Bearer $accessToken';

 super.onRequest(options, handler);
}

@override
void onError(DioError err, ErrorInterceptorHandler handler) {
switch (err.type) {
  case DioErrorType.connectTimeout:
  case DioErrorType.sendTimeout:
  case DioErrorType.receiveTimeout:
    throw DeadlineExceededException(err.requestOptions);
  case DioErrorType.response:
    switch (err.response?.statusCode) {
      case 400:
        throw BadRequestException(err.requestOptions);
      case 401:
        throw UnauthorizedException(err.requestOptions);
      case 404:
        throw NotFoundException(err.requestOptions);
      case 409:
        throw ConflictException(err.requestOptions);
      case 500:
        throw InternalServerErrorException(err.requestOptions);
    }
    break;
  case DioErrorType.cancel:
    break;
  case DioErrorType.other:
    throw NoInternetConnectionException(err.requestOptions);
}
super.onError(err, handler);
}
}
...
...

This is how I done my Dio Interceptor, you don't have to return anything in your void onRequest() simply call super.onRequest() and don't use handler instance in interceptor class like

return handler.resolve(cloneReq);

that part is already done inside onRequest(). I solved my problem in this way you can also try.

thank you.

Related issue: https://github.com/flutterchina/dio/issues/1480

There are several open PRs that (try to) tackle this bug:

If you do not want to downgrade to dio 4.0.4 as other answers suggest, you can depend on some of these forks until one of them is merged into the official repository.

In my case, I've reviewed and tested @ipcjs's solution and seems to be working as expected:

dio:
  git:
    url: https://github.com/ipcjs/dio
    path: dio/
    ref: b77af132442bf3266ccf11b50ce909711455db3a

use QueuedInterceptor try it 0-

To solve this error, I did like that

void onError(DioError err, ErrorInterceptorHandler handler) async {
  //Halding refresh token other logic
 
//Future.delay solve my error.
  Future.delayed(const Duration(seconds: 5), () => super.onError(err,handler));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM