Connection closed before full header was received http post in flutter - flutter

I'm having problems trying to consume my API, and after following up on this problem I'm stuck in the matter and I've tried different emulators in various versions and the problem persists.
Error:
DioError [DioErrorType.other]: HttpException: Connection closed before full header was received, uri = http://10.0.2.2:7108/Users/authenticate
Flutter doctor
Http Post
class AuthenticateRemoteApi extends AuthenticateGateway {
final AuthenticateMapper _authenticateMapper = AuthenticateMapper();
#override
Future<SesionUser> login(Authenticate user) async {
var dio = Dio();
dio.options.headers['content-Type'] = 'application/json';
String url = 'http://10.0.2.2:7108/Users/authenticate';
try {
Response response = await dio.post(url, data: authenticateModelToJson(user));
return _authenticateMapper.fromMap(jsonDecode(response.data));
} catch (e) {
throw Exception(e);
}
}
}

I have came with this work around by creating this interceptor.
The idea of it is when encountering this random error just to retry the request.
/// Interceptor
class RetryOnConnectionChangeInterceptor extends Interceptor {
final Dio dio;
RetryOnConnectionChangeInterceptor({
required this.dio,
});
#override
void onError(DioError err, ErrorInterceptorHandler handler) async {
if (_shouldRetryOnHttpException(err)) {
try {
handler.resolve(await DioHttpRequestRetrier(dio: dio).requestRetry(err.requestOptions).catchError((e) {
handler.next(err);
}));
} catch (e) {
handler.next(err);
}
} else {
handler.next(err);
}
}
bool _shouldRetryOnHttpException(DioError err) {
return err.type == DioErrorType.other &&
((err.error is HttpException && err.message.contains('Connection closed before full header was received')));
}
}
/// Retrier
class DioHttpRequestRetrier {
final Dio dio;
DioHttpRequestRetrier({
required this.dio,
});
Future<Response> requestRetry(RequestOptions requestOptions) async {
return dio.request(
requestOptions.path,
cancelToken: requestOptions.cancelToken,
data: requestOptions.data,
onReceiveProgress: requestOptions.onReceiveProgress,
onSendProgress: requestOptions.onSendProgress,
queryParameters: requestOptions.queryParameters,
options: Options(
contentType: requestOptions.contentType,
headers: requestOptions.headers,
sendTimeout: requestOptions.sendTimeout,
receiveTimeout: requestOptions.receiveTimeout,
extra: requestOptions.extra,
followRedirects: requestOptions.followRedirects,
listFormat: requestOptions.listFormat,
maxRedirects: requestOptions.maxRedirects,
method: requestOptions.method,
receiveDataWhenStatusError: requestOptions.receiveDataWhenStatusError,
requestEncoder: requestOptions.requestEncoder,
responseDecoder: requestOptions.responseDecoder,
responseType: requestOptions.responseType,
validateStatus: requestOptions.validateStatus,
),
);
}
}
Usage: add this interceptor [RetryOnConnectionChangeInterceptor] to your Dio client instance

I think this could be a package bug, usually appears when the user doesn't has internet connect. Reported in #1269 and #377 issue from the package repository. I recommend you to use another package.

In my case this error always occurs when I build app on emulator, rarely appears on real device. If you are using Android Studio, try with Pixel 6 API 33.

Related

http put did not send any response in flutter

Hey I have this app where I can update status, and I use http.put method, but it takes forever to response. I got this error
error
And here is the code for http.put
Future<void> mapEventToState(
Emitter<ReportStatusState> emit, ReportStatusEvent event) async {
emit(ReportStatusLoading());
ReportStatusPut statusPut = event.statusPutBody;
// ReportStatusModel model =
// await apiAuthRepository.updateReportStatus(statusPut, event.id);
ReportStatusModel model = await updateReportStatus({'biaya': '0', 'status': 'SELESAI'}, event.id);
print(model);
if (!model.success) {
emit(ReportStatusFailure(model.message));
}
print(model.code);
emit(ReportStatusSuccess());
}}
Future<ReportStatusModel> updateReportStatus(
Map data, String id) async {
final SharedPreferencesManager sharedPreferencesManager =
locator<SharedPreferencesManager>();
String? token =
sharedPreferencesManager.getString(SharedPreferencesManager.keyAccessToken);
try {
final response = await http.put(
Uri.parse('https://api.komplekku.com/officer/api/report/v1/$id'),
body: json.encode(data),
headers: {'Authorization': 'Bearer $token'});
return ReportStatusModel.fromJson(json.decode(response.body));
} catch (e) {
throw Exception(e);
}
}
There is nothing wrong with the API, I already check using Postman and it worked perfectly fine, Anyone know what went wrong?

Dio Client: if request to protected route fails (401 code), then refresh the token and try again. Struggling to create

I am trying to create a custom ApiClient class that I can inject as a dependency (with get_it package) to be used in the data layer of my application. In order not to worry about access tokens throughout the presentation/application/domain layers of my app, I'd like to have a field, accessToken, that keeps track of the accessToken inside the ApiClient (singleton) class.
The ApiClient class would be used all throughout my data layer to handle requests to my server for data. It should have a method that allows me to add my own requests to it for unique routes. Then, if those routes require access tokens, it will add the accessToken field from the class along with the request. If that access token is invalid (expired/tampered with), then I would use the refresh token from the device's storage and send a request to the server to get a new access token, then try the original request again. It would "retry" the request at maximum once. Then, if there's still an error, it just returns that to be handled.
I am really struggling with how to implement this. My current attempt is below. Any help would be amazing!
class ApiClient {
final String baseUrl;
final Dio dio;
final NetworkInfo networkInfo;
final FlutterSecureStorage secureStorage;
ApiClient(
{required this.baseUrl,
required this.dio,
required this.networkInfo,
required this.secureStorage}) {
dio.interceptors.add(RefreshInvalidTokenInterceptor(networkInfo, dio, secureStorage));
}
}
class RefreshInvalidTokenInterceptor extends QueuedInterceptor {
final NetworkInfo networkInfo;
final Dio dio;
final FlutterSecureStorage secureStorage;
String? accessToken;
RefreshInvalidTokenInterceptor(this.networkInfo, this.dio, this.secureStorage);
#override
Future onError(DioError err, ErrorInterceptorHandler handler) async {
if (_shouldRetry(err) && await networkInfo.isConnected) {
try {
// access token request (using refresh token from flutter_secure_storage)
final refreshToken = await secureStorage.read(key: "refreshToken");
final response = await dio.post(
"$kDomain/api/user/token",
queryParameters: {"token": refreshToken},
);
accessToken = response.data["accessToken"];
return err;
} on DioError catch (e) {
handler.next(e);
} catch (e) {
handler.next(err);
}
} else {
handler.next(err);
}
}
bool _shouldRetry(DioError err) =>
(err.response!.statusCode == 403 || err.response!.statusCode == 401);
}
There are similar questions online, but none seem to answer my question! :)
EDIT: I've gotten a working solution (almost), with just 1 error. This works (except in the function retryRequest() I'm hardcoding the request to be a post request):
<imports removed for simplicity>
class ApiClient {
final Dio dio;
final NetworkInfo networkInfo;
final FlutterSecureStorage secureStorage;
String? accessToken;
ApiClient({
required this.dio,
required this.networkInfo,
required this.secureStorage,
}) {
dio.options = BaseOptions(
connectTimeout: 5000,
receiveTimeout: 3000,
receiveDataWhenStatusError: true,
followRedirects: true,
headers: {"content-Type": "application/json"},
);
dio.interceptors.add(QueuedInterceptorsWrapper(
//! ON REQUEST
onRequest: (options, handler) {
handler.next(options);
},
//! ON RESPONSE
onResponse: (response, handler) {
print("onResponse...");
handler.next(response);
},
//! ON ERROR
onError: (error, handler) async {
print("onError...");
if (tokenInvalid(error)) {
print("token invalid: retrying");
print("header before: ${dio.options.headers}");
await getAccessTokenAndSetToHeader(dio);
print("header after: ${dio.options.headers}");
final response = await retryRequest(error, handler);
handler.resolve(response);
print("here-1");
} else {
handler.reject(error);
}
print("here-2");
print("here-3");
},
));
}
Future<String?> getRefreshToken() async => await secureStorage.read(key: "refreshToken");
Future<void> getAccessTokenAndSetToHeader(Dio dio) async {
final refreshToken = await secureStorage.read(key: "refreshToken");
if (refreshToken == null || refreshToken.isEmpty) {
print("NO REFRESH TOKEN ERROR; LOGOUT!!!");
throw ServerException();
} else {
final response = await dio.post(
"$kDomain/api/user/token",
data: {"token": refreshToken},
);
dio.options.headers["authorization"] = "Bearer ${response.data["accessToken"]}";
}
}
// This function has the hardcoded post
Future<Response> retryRequest(DioError error, ErrorInterceptorHandler handler) async {
print("retry called, headers: ${dio.options.headers}");
final retryResponse = await dio.post(error.requestOptions.path);
print("retry results: $retryResponse");
return retryResponse;
}
bool tokenInvalid(DioError error) =>
error.response?.statusCode == 403 || error.response?.statusCode == 401;
Future<void> refreshToken() async {}
bool validStatusCode(Response response) =>
response.statusCode == 200 || response.statusCode == 201;
}
However, if I change the hardcoded post request to:
final retryResponse =
await dio.request(error.requestOptions.path, data: error.requestOptions.data);
the code no longer works... anyone know why? Having it dynamic based on whatever the failed request was, lets me re-use this class.
package:dio already include the BaseOptions which you can use to add some basic configuration like the baseUrl.
After that, you could use interceptors to add the accessToken to every request. To do this depending on your state management solution you can update the accessToken when the user authentication state changes.
And finally regarding the token refresh you can checkout package:fresh_dio.
Figured it out! (code + how to use below)
Here is my entire ApiClient class (imports hidden for simplicity). It acts as an HTTP client using dio:
class ApiClient {
final Dio dio;
final NetworkInfo networkInfo;
final FlutterSecureStorage secureStorage;
String? accessToken;
/// The base options for all requests with this Dio client.
final BaseOptions baseOptions = BaseOptions(
connectTimeout: 5000,
receiveTimeout: 3000,
receiveDataWhenStatusError: true,
followRedirects: true,
headers: {"content-Type": "application/json"},
baseUrl: kDomain, // Domain constant (base path).
);
/// Is the current access token valid? Checks if it's null, empty, or expired.
bool get validToken {
if (accessToken == null || accessToken!.isEmpty || Jwt.isExpired(accessToken!)) return false;
return true;
}
ApiClient({
required this.dio,
required this.networkInfo,
required this.secureStorage,
}) {
dio.options = baseOptions;
dio.interceptors.add(QueuedInterceptorsWrapper(
// Runs before a request happens. If there's no valid access token, it'll
// get a new one before running the request.
onRequest: (options, handler) async {
if (!validToken) {
await getAndSetAccessTokenVariable(dio);
}
setHeader(options);
handler.next(options);
},
// Runs on an error. If this error is a token error (401 or 403), then the access token
// is refreshed and the request is re-run.
onError: (error, handler) async {
if (tokenInvalidResponse(error)) {
await refreshAndRedoRequest(error, handler);
} else {
// Other error occurs (non-token issue).
handler.reject(error);
}
},
));
}
/// Sets the current [accessToken] to request header.
void setHeader(RequestOptions options) =>
options.headers["authorization"] = "Bearer $accessToken";
/// Refreshes access token, sets it to header, and resolves cloned request of the original.
Future<void> refreshAndRedoRequest(DioError error, ErrorInterceptorHandler handler) async {
await getAndSetAccessTokenVariable(dio);
setHeader(error.requestOptions);
handler.resolve(await dio.post(error.requestOptions.path,
data: error.requestOptions.data, options: Options(method: error.requestOptions.method)));
}
/// Gets new access token using the device's refresh token and sets it to [accessToken] class field.
///
/// If the refresh token from the device's storage is null or empty, an [EmptyTokenException] is thrown.
/// This should be handled with care. This means the user has somehow been logged out!
Future<void> getAndSetAccessTokenVariable(Dio dio) async {
final refreshToken = await secureStorage.read(key: "refreshToken");
if (refreshToken == null || refreshToken.isEmpty) {
// User is no longer logged in!
throw EmptyTokenException();
} else {
// New DIO instance so it doesn't get blocked by QueuedInterceptorsWrapper.
// Refreshes token from endpoint.
try {
final response = await Dio(baseOptions).post(
"/api/user/token",
data: {"token": refreshToken},
);
// If refresh fails, throw a custom exception.
if (!validStatusCode(response)) {
throw ServerException();
}
accessToken = response.data["accessToken"];
} on DioError catch (e) {
// Based on the different dio errors, throw custom exception classes.
switch (e.type) {
case DioErrorType.sendTimeout:
throw ConnectionException();
case DioErrorType.connectTimeout:
throw ConnectionException();
case DioErrorType.receiveTimeout:
throw ConnectionException();
case DioErrorType.response:
throw ServerException();
default:
throw ServerException();
}
}
}
}
bool tokenInvalidResponse(DioError error) =>
error.response?.statusCode == 403 || error.response?.statusCode == 401;
bool validStatusCode(Response response) =>
response.statusCode == 200 || response.statusCode == 201;
}
It should be injected as a singleton to your project so there's one instance of it (for the sake of keeping the state of its accessToken field). I used get_it like so:
// Registers the custom ApiClient class.
sl.registerLazySingleton(() => ApiClient(dio: sl(), networkInfo: sl(), secureStorage: sl()));
Then, inside your data layer (or wherever you call APIs from), you can use it by passing it through the constructor:
class MyDatasource implements IMyDatasource {
final ApiClient apiClient;
late Dio api;
FeedDatasource({required this.client, required this.apiClient}) {
api = apiClient.dio;
}
// Logic for your class here.
}
I simplified it to api so I wouldn't have to go apiClient.dio... every call (optional).
Then, you can use it in one of your class' methods like so:
#override
Future<List<SomeData>> fetchSomeDataFromApi() async {
try {
final response = await api.post("/api/data/whatYouWant");
throw ServerException();
} catch (e) {
throw ServerException();
}
}
Now, for this request, if your class has a valid access token (non-null, non-empty, non-expired), it will call normally. However, if your token isn't valid, it'll refresh it first, then proceed with your call. Even if the call fails after the token originally passed the validation check (token somehow expires during the call for example), it will still be refreshed, and the call re-executed.
Note: I use a lot of custom exceptions, this is optional.
Hopefully this helps someone else!

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:
IN MY CASE the problem was in the first
return super.onError(err, handler);
It must be return null;
So I got it working
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
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.
Update 13/02/23:
dio v5.0.0 finally contains a fix for this issue.
Details: At the end flutter-china has transferred the ownership of the dio repo to CFUG and all the changes from the diox hard fork have been merged into the original dio repo, including the fix for this issue.
Update 15/12/22:
diox is a hard fork of dio made by CFUG group with the aim of keeping dio well maintained. In diox, this issue has already been fixed.
Original answer:
Related issue: https://github.com/flutterchina/dio/issues/1480
There are several open PRs that (try to) tackle this bug:
https://github.com/flutterchina/dio/pull/1470
https://github.com/flutterchina/dio/pull/1496
https://github.com/flutterchina/dio/pull/1550
https://github.com/flutterchina/dio/pull/1565
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
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.
To instantly solve this problem just comment out the "connectTimeOut" field from DioBaseOptions as follows:
connectTimeout: 30000,
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));
}

Flutter how can i set Auth token from flutter secure storage to dio header?

After login i setting user token to my user Secure storage. Like :
Future<AuthResponseModel?> login(AuthRequstModel model) async {
try {
Response response = await _dio.post(loginPath, data: model);
if (response.statusCode == 200) {
final AuthResponseModel authResponseModel = AuthResponseModel.fromJson(response.data);
if (authResponseModel.success!) {
await UserSecureStorage.setField("token", authResponseModel.token);
}
return AuthResponseModel.fromJson(response.data);
}
return null;
} catch (e) {
return null;
}
}
User Secure Storage =>
class UserSecureStorage {
static const _storage = FlutterSecureStorage();
static Future setField(String key, value) async {
await _storage.write(key: key, value: value);
}
static Future<String?> getField(key) async {
return await _storage.read(key: key);
}
But problem is when i want to make apiservice and when i want to auth token inside header of dio, I cant access it becouse its a future<String?> function. But i cant use await coz its inside of baseoption. Like :
class ApiService {
final _dio = Dio(BaseOptions(headers: {
'authorization': 'Bearer ${UserSecureStorage.getField("token")}', //I cant access here its only giving instance.
}));
Future<Response?> get(String path) async {
try {
Response response = await _dio.get('${ApiConstants.BASE_URL}$path');
if (response.statusCode == 200) {
return response;
}
return null;
} on DioError catch (e) {
return null;
}
}
What can i do for solve that problem ? I tried use .then(value=>value) after tried get token but didnt work too. Thanks for responses!
I think token is not getting updated because _dio is already intitalized.
Try to request for token when dio request is made like :
class ApiService {
final _dio = Dio();
Future<Response?> get(String path) async {
try {
Response response = await _dio.get('${ApiConstants.BASE_URL}$path', options: Options(headers: {"authorization": "Bearer ${UserSecureStorage.getField("token")}"}));
if (response.statusCode == 200) {
return response;
}
return null;
} on DioError catch (e) {
return null;
}
}
Use options in get method to add headers for a single request or interceptors for all requests.
I think that it is not an issue easily solvable, I would try with two different methods, you can maintain the token in a state manager such as Provider so you don't have to rely on an async function to retrive it, but this of course add in the code the state manager structure that complicates thing a little.
A bit more naive way to solve this could be to include a async initializator in the ApiService class such this
class ApiService {
late final _dio;
Future<void> init() async {
_dio = Dio(BaseOptions(headers: {
'authorization': 'Bearer ${UserSecureStorage.getField("token")}', //I cant access here its only giving instance.
}));}
Future<Response?> get(String path) async {
try {
Response response = await _dio.get('${ApiConstants.BASE_URL}$path');
if (response.statusCode == 200) {
return response;
}
return null;
} on DioError catch (e) {
return null;
}
}
And this introduce us a new issue, we have to call init everytime the class ApiService is instantiated, to solve this you could use the package get_it which grants you the possibility to instatiate only once the class and access it from everywhere in your project.
I hope this will help you solve your problem
your are getting instance because UserSecureStorage.getField("token") is future so you can get token when you put await keyword
so try like this
await UserSecureStorage.getField("token")

How do I implement dio http cache alongside my interceptor

Here is my interceptor setup :
class AppInterceptor extends Interceptor {
Dio dio = Dio();
Dio previous;
AppInterceptor() {}
AppInterceptor.firebaseIDToken() {
this.dio.interceptors.add(
InterceptorsWrapper(onRequest: (options, handler) async {
var token = await getAuthorizationToken();
options.headers["Authorization"] = 'Bearer $token';
dio.unlock();
handler.next(options);
}, onResponse: (response, handler) {
return handler.next(response);
}, onError: (DioError e, handler) {
return handler.next(e);
}
),
);
}
...
}
And here is how I make http request:
Response response;
if (user != null) {
response = await AppInterceptor.tokenAuthorization()
.dio.get(Global.apiurl + 'jobs/detail/$pageid?
coordinates=$coordinates');
} else {
response = await AppInterceptor.apikey().dio.get(Global.apiurl +
'jobs/detail/$pageid?coordinates=$coordinates');
}
return Job.fromJson(response.data);
Now what I want to do is add the dio HTTP cache interceptor
https://pub.dev/packages/dio_http_cache
dio.interceptors.add(DioCacheManager(CacheConfig(baseUrl: "http://www.google.com")).interceptor);
google.com here should be my Global.apiurl
My question is, how do I go about adding this to my above implementation?
I stumbled upon this problem earlier and actually found my way through Dio's doc. I learned that handler actually have method other than next which is resolve.
To resolve onError with custom Response (in this case, something from your cache), you should resolve the handler instead of passing it through next.
I made the interceptor as it's own class and specifically return cached response only on Connection Timeout & Other Error.
class CacheInterceptor extends Interceptor {
final _cache = <Uri, Response>{};
#override
onRequest(options, handler) => handler.next(options);
#override
onResponse(response, handler) {
// Cache the response with uri as key
_cache[response.requestOptions.uri] = response;
handler.resolve(response);
}
#override
onError(DioError err, handler) {
var isTimeout = err.type == DioErrorType.connectTimeout;
var isOtherError = err.type == DioErrorType.other;
if (isTimeout || isOtherError) {
// Read cached response if available by uri as key
var cachedResponse = _cache[err.requestOptions.uri];
if (cachedResponse != null) {
// Resolve with cached response
return handler.resolve(cachedResponse);
}
}
return handler.next(err);
}
}
And then, add the CacheInterceptor above to the Dio instance
...
dio.interceptors
..add(CacheInterceptor())
..add(LogInterceptor());
...