So, I have an interceptor set for api calls. It looks like this:
class AuthorizationInterceptor extends Interceptor {
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
if (options.headers.containsKey('requiresToken') &&
options.headers['requiresToken'] == false) {
options.headers.remove('requiresToken');
super.onRequest(options, handler);
} else {
String token = await SecureStorage.loadAccessToken();
options.headers['Authorization'] = 'Bearer $token';
// options.headers['Content-Type'] = 'application/json';
super.onRequest(options, handler);
}
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
log('++++++ interceptor error ++++++');
if (await SecureStorage.loadAccessToken() == '') {
super.onError(err, handler);
return;
}
bool isTokenRefreshed = await AuthApi.refreshToken();
if (isTokenRefreshed) {
RequestOptions origin = err.response!.requestOptions;
String token = await SecureStorage.loadAccessToken();
origin.headers["Authorization"] = "Bearer $token";
try {
final Response response = await DioClient.request(
url: origin.path,
data: origin.data,
options: Options(
headers: origin.headers,
method: origin.method,
),
);
handler.resolve(response);
} catch (e) {
super.onError(err, handler);
}
}
} else {
super.onError(err, handler);
return;
}
}
}
Now, when I’m calling some api with dio GET method and the token is expired, onError interceptor handles 401 and refreshes the token. After that the request that was previously called continues and everything finishes fine.
But, when I try to do the exact thing using dio POST it doesn’t work as expected. If there’s a 401 response code it should go through onError and refresh the token and then continue to call previously called POST function which looks like this:
static Future uploadImage(PlatformFile image, String disclaimer,
{String? imageTitle}) async {
String imageExtension = image.extension!;
String imageName = '${imageTitle ?? 'image'}.$imageExtension';
final formData = FormData.fromMap({
'upload_file': MultipartFile.fromBytes(
image.bytes!,
filename: imageName,
contentType: MediaType('media_content', imageExtension),
),
'disclaimer': disclaimer,
});
try {
final response = await DioClient.post(
url: Endpoint.images,
data: formData,
options: Options(
headers: {
'Content-Type': 'multipart/form-data',
},
),
);
return response.data;
} on DioError catch (err) {
ToastMessage.apiError(err);
log('DioError uploadImage response: ${ToastMessage.message}');
}
}
This is one of the functions, as many others I use, that works fine:
static Future getPosts(
{required int page,
int? pageSize,
String? searchParam,
String? status,
String? categoryId}) async {
try {
final response = await DioClient.get(
url: Endpoint.getPosts,
query: {
'page': page,
if (pageSize != null) 'page_size': pageSize,
if (status != null) 'status': status,
if (searchParam != null) 'search_param': searchParam,
if (categoryId != null) 'category_id': categoryId,
},
);
return response.data;
} on DioError catch (err) {
ToastMessage.apiError(err);
log('DioError get posts response: ${ToastMessage.message}');
}
}
I tried everything so far. Everything I do looks like this:
When calling dio GET functions and the response is 401 this is the flow in logs:
- DioError is caught and it enters onError of the interceptor
- checks if error is 401 and refreshes the token
- loads the token and calls initial GET function again and returns expected values
When calling dio POST (above uploadImage function):
- if response is 401 IT DOESN’T enter onError of the interceptor but immediately calls ToastMessage and shows the user the upload process wasn’t finish (which it really wasn’t)
- after this happens THEN IT ENTERS onError interceptor and refreshes the token
So, my question would probably be:
Why is onError of the DioError interceptor not called if the response code is 401 in POST function but is called in GET functions?
UPDATE:
When 401 is the response of the uploadImage function this is the flow:
- it enters the interceptor
- refreshes the token
- after successful token refresh it enters try block and retries to call uploadImage again with correct request options
- SUDDENLY IT JUMPS BACK AT THE TOP of the onError interceptor (this implies that try block didn’t pass eventhough I got no errors of any kind)
- goes back to uploadImage’s DioError and returns ToastMessage
In my IDE’s logs I see that the call in this try block is done but in my browser’s network inspection nothing happens. I just have 401 response from uploadImage BE and 200 response for refresh token response and no retried uploadImage call.
UPDATE 2:
My issue is the same as described here
2
Answers
After some research I found out that multipart files are Stream and they need to be re-instantiated when retrying an API call after token refresh.
So, I managed to solve my problem and these are updated functions in case someone else stumble upon this problem.
Then this is new interceptor onError part:
The file that keeps FormDataHandler:
I’m not sure but I’ve just checked my implementation on 401 handling and I use:
instead:
here is part from my code