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();
}
}
2
Answers
An API service should be in the model layer, and should not need a BlockContext, 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.
I believe that you should isolate your BuildContext inside the UI level and shouldn’t let your API service know about it. That would be a bad practice. Maybe a simple suggestion: You can use a controller (BLoC / Cubit etc.) and emit the state to the UI in order to act accordingly. Once you receive an error from the API Service, your controller can let your UI know that an error was received. Thus, your UI can show the necessary Popup/Toast Message.