I have an Flutter app - simple eshop. I need to do a simple call of logout when refresh token expires, but problem is that BuildContext is not available in ApiService where is received info about expired refresh token. I tried BuildContextProvider but it is not called. Does it needs to be somehow initialized, or other solution ?
class ApiService {
late final Dio dio;
static ApiService? _instance;
factory ApiService() => _instance ??= ApiService._();
ApiService._() {
dio = _createDio();
}
Dio _createDio() {
var dio = Dio(BaseOptions(
baseUrl: FlavorSettings().baseUrl,
headers: {
"Accept": "*/*",
"Content-Type": "application/json",
"Connection": "keep-alive",
},
followRedirects: true,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
sendTimeout: const Duration(seconds: 15)))
..interceptors.add(LogInterceptor(requestBody: true, responseBody: true))
..interceptors.add(DioFirebasePerformanceInterceptor());
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(HttpClient client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
};
dio.interceptors.addAll({
AppInterceptors(dio),
});
return dio;
}
Future<Response> request(String url,
{dynamic body, String? method, Map<String, dynamic>? headers}) async {
var res = dio.request(url,
data: body,
options: Options(
method: method,
headers: headers,
));
return res;
}
Future<String> downloadFile(String url, String name) async {
String dir = (await getTemporaryDirectory()).path;
var response = await dio.download(
url,
'$dir/$name',
);
Fimber.d(
"File download url; $url name: $name ${response.statusCode} ${response.statusMessage}");
return '$dir/$name';
}
NetworkError? getApiError(Response response) {
if (response.data == null) {
return null;
} else {
try {
NetworkError error = NetworkError.fromJson(response.data);
return error;
} catch (e) {
Fimber.d(
"getApiError ${response.statusCode} ${response.statusMessage} ${response.toString()}");
return null;
}
}
}
}
class AppInterceptors extends Interceptor {
final Dio dio;
AppInterceptors(this.dio);
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
options.headers['X-Caller'] = UserRepository().xCaller;
return handler.next(options);
}
@override
void onError(
DioException dioException, ErrorInterceptorHandler handler) async {
Fimber.e('API_ERROR:', ex: dioException);
// refresh token
if (dioException.type == DioExceptionType.badResponse &&
dioException.response?.statusCode == 401) {
Fimber.w("401 unauth");
_refreshToken();
_retry(dioException.requestOptions);
}
if (dioException.type == DioExceptionType.badResponse &&
dioException.response?.statusCode == 400 &&
dioException.requestOptions.path.contains("refreshToken")) {
Fimber.w("api_auth_service 400 unauth");
BuildContextProvider()((context) => {
context.read<LoginCubit>().logout(context),
Device.showToast(context, "Platnosť prihlásenia vypršala.")
});
UserRepository().logout();
}
return handler.next(dioException);
}
Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
final options = Options(
method: requestOptions.method,
headers: requestOptions.headers,
);
return dio.request<dynamic>(requestOptions.path,
data: requestOptions.data,
queryParameters: requestOptions.queryParameters,
options: options);
}
Future<void> _refreshToken() async {
await UserRepository().refreshToken();
}
}
An API service should be in the model layer, and should not need a BuildContext, which is needed only in the view layer. The proper protocol is the model should react with a distinct value, and the view can notice that and update to report that.