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 .
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.