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 '../app/app.locator.dart'; import '../ui/common/app_constants.dart'; class DioService { // Dependency injection final _navigationService = locator(); final _authenticationService = locator(); // Initialization final Dio _dio = Dio(); Dio get dio => _dio; final Dio _refreshDio = Dio(); // separate instance bool _isRefreshing = false; final List _retryQueue = []; // Initialization DioService() { _dio.options ..baseUrl = kBaseUrl ..connectTimeout = const Duration(seconds: 30) ..receiveTimeout = const Duration(seconds: 30); _dio.interceptors.add( InterceptorsWrapper( onError: _onError, onRequest: _onRequest, onResponse: _onResponse, ), ); } // Response logger 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 access = await _authenticationService.getAccessToken(); final refresh = await _authenticationService.getRefreshToken(); if (access != null) { options.headers['Authorization'] = 'Bearer $access'; } options.headers['Accept'] = 'application/json'; options.headers['Content-Type'] = 'application/json'; debugPrint('️️➡️➡️➡️➡️INITIALIZING REQUEST➡️➡️➡️➡️'); debugPrint('➡️ ${options.method} ${options.uri}'); debugPrint('➡️ REFRESH: $refresh'); debugPrint('➡️ HEADERS: ${options.headers}'); debugPrint('➡️ DATA: ${options.data}'); debugPrint('️️➡️➡️➡️➡️FINALIZING REQUEST➡️➡️➡️➡️'); handler.next(options); } // Error logger 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; } } // Refresh token Future _refreshToken() async { final UserModel? user = await _authenticationService.getUser(); if (user?.refreshToken == null) return false; try { Map data = { 'role': 'USER', 'user_id': user?.userId, 'access_token': user?.accessToken, 'refresh_token': user?.refreshToken }; final response = await _refreshDio.post( '$kBaseUrl/$kRefreshTokenUrl', data: data, ); await _authenticationService.saveTokens( access: response.data['access_token'], refresh: response.data['refresh_token'], ); return true; } catch (e) { await _authenticationService.logOut(); await _navigationService.replaceWithLoginView(); return false; } } // Check request if immediately after token refreshed bool _isRefreshRequest(RequestOptions options) { return options.path.contains(kRefreshTokenUrl); } }