import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/models/user_model.dart'; import 'package:yimaru_app/services/authentication_service.dart'; import 'package:yimaru_app/services/secure_storage_service.dart'; import '../app/app.locator.dart'; import '../ui/common/app_constants.dart'; class DioService { final _navigationService = locator(); final _authenticationService = locator(); final Dio _dio = Dio(); final Dio _refreshDio = Dio(); // separate instance bool _isRefreshing = false; final List _retryQueue = []; DioService() { _dio.options ..baseUrl = baseUrl ..connectTimeout = const Duration(seconds: 30) ..receiveTimeout = const Duration(seconds: 30); _dio.interceptors.add( InterceptorsWrapper( onError: _onError, onRequest: _onRequest, onResponse: _onResponse, ), ); } void _onResponse( Response response, ResponseInterceptorHandler handler, ) { debugPrint('✅✅✅✅INITIALIZING RESPONSE✅✅✅✅'); debugPrint('✅ ${response.statusCode} ${response.requestOptions.uri}'); debugPrint('✅ DATA: ${response.data}'); debugPrint('✅✅✅✅FINALIZING RESPONSE✅✅✅✅'); handler.next(response); } Future _onRequest( RequestOptions options, RequestInterceptorHandler handler, ) async { final token = await _authenticationService.getAccessToken(); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } options.headers['Accept'] = 'application/json'; options.headers['Content-Type'] = 'application/json'; debugPrint('️️➡️➡️➡️➡️INITIALIZING REQUEST➡️➡️➡️➡️'); debugPrint('➡️ ${options.method} ${options.uri}'); debugPrint('➡️ HEADERS: ${options.headers}'); debugPrint('➡️ DATA: ${options.data}'); debugPrint('️️➡️➡️➡️➡️FINALIZING REQUEST➡️➡️➡️➡️'); handler.next(options); } Future _onError( DioException error, ErrorInterceptorHandler handler, ) async { debugPrint('❌❌❌❌INITIALIZING ERROR❌❌❌❌'); debugPrint('❌ ${error.response?.data} ${error.requestOptions.uri}'); debugPrint('❌ ${error.response?.statusCode} ${error.requestOptions.uri}'); debugPrint('❌❌❌❌FINALIZING ERROR❌❌❌❌'); if (error.response?.statusCode == 401 && !_isRefreshRequest(error.requestOptions)) { return _handle401(error, handler); } handler.next(error); } Future _handle401( DioException error, ErrorInterceptorHandler handler, ) async { final requestOptions = error.requestOptions; if (_isRefreshing) { _retryQueue.add(() async { final response = await _dio.fetch(requestOptions); handler.resolve(response); }); return; } _isRefreshing = true; try { final refreshed = await _refreshToken(); if (!refreshed) { handler.reject(error); return; } final response = await _dio.fetch(requestOptions); for (final retry in _retryQueue) { retry(); } _retryQueue.clear(); handler.resolve(response); } catch (e) { handler.reject(error); } finally { _isRefreshing = false; } } Future _refreshToken() async { final UserModel user = await _authenticationService.getUser(); if (user.refreshToken == null) return false; try { Map data = { 'role': 'STUDENT', 'user_id': user.userId, 'access_token': user.accessToken, 'refresh_token': user.refreshToken }; print(data); final response = await _refreshDio.post( '$baseUrl/$kRefreshTokenUrl', data: data, options: Options( followRedirects: false, validateStatus: (status) => true, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, ), ); print('Refresh response'); print(response.data); await _authenticationService.saveTokens( access: response.data['access_token'], refresh: response.data['refresh_token'], ); return true; } catch (e) { print('Refresh response exception'); print(e.toString()); // await _authenticationService.logOut(); // await _navigationService.replaceWithLoginView(); return false; } } bool _isRefreshRequest(RequestOptions options) { return options.path.contains(kRefreshTokenUrl); } Dio get dio => _dio; }