fix : add cancel Token
This commit is contained in:
@@ -33,12 +33,8 @@ export 'package:rasadyar_core/presentation/common/common.dart';
|
||||
export 'package:rasadyar_core/presentation/utils/utils.dart';
|
||||
export 'package:rasadyar_core/presentation/widget/widget.dart';
|
||||
|
||||
export 'infrastructure/local/hive_local_storage.dart';
|
||||
//network
|
||||
export 'infrastructure/remote/dio_form_data.dart';
|
||||
export 'infrastructure/remote/dio_remote.dart';
|
||||
export 'infrastructure/remote/dio_response.dart';
|
||||
export 'injection/di.dart';
|
||||
//infrastructure
|
||||
export 'infrastructure/infrastructure.dart';
|
||||
|
||||
///image picker
|
||||
export 'package:image_picker/image_picker.dart';
|
||||
|
||||
14
packages/core/lib/infrastructure/infrastructure.dart
Normal file
14
packages/core/lib/infrastructure/infrastructure.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
// local
|
||||
export 'local/hive_local_storage.dart';
|
||||
export 'local/i_local_storage.dart';
|
||||
|
||||
//remote
|
||||
export 'remote/interfaces/i_form_data.dart';
|
||||
export 'remote/interfaces/i_http_client.dart';
|
||||
export 'remote/interfaces/i_http_response.dart';
|
||||
export 'remote/interfaces/i_remote.dart';
|
||||
|
||||
export 'remote/app_interceptor.dart';
|
||||
export 'remote/dio_form_data.dart';
|
||||
export 'remote/dio_remote.dart';
|
||||
export 'remote/dio_response.dart';
|
||||
51
packages/core/lib/infrastructure/remote/app_interceptor.dart
Normal file
51
packages/core/lib/infrastructure/remote/app_interceptor.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'dart:async';
|
||||
import '../../core.dart';
|
||||
|
||||
typedef RefreshTokenCallback = Future<String?> Function();
|
||||
class AppInterceptor extends Interceptor {
|
||||
final RefreshTokenCallback refreshTokenCallback;
|
||||
|
||||
AppInterceptor({required this.refreshTokenCallback});
|
||||
|
||||
@override
|
||||
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
if (err.response?.statusCode == 401 && !ApiHandler.isRefreshing) {
|
||||
// اول بقیه درخواستها رو کنسل کن
|
||||
ApiHandler.cancelAllRequests("Token expired - refreshing");
|
||||
|
||||
ApiHandler.isRefreshing = true;
|
||||
|
||||
try {
|
||||
final newToken = await refreshTokenCallback();
|
||||
if (newToken == null) throw Exception("Refresh failed");
|
||||
|
||||
// تولید CancelToken جدید برای درخواستهای بعدی
|
||||
ApiHandler.reset();
|
||||
|
||||
final opts = err.requestOptions;
|
||||
opts.headers['Authorization'] = 'Bearer $newToken';
|
||||
|
||||
|
||||
final dio = Dio();
|
||||
final cloneReq = await dio.fetch(opts);
|
||||
handler.resolve(cloneReq);
|
||||
return;
|
||||
} catch (e) {
|
||||
if (!ApiHandler.isRedirecting) {
|
||||
ApiHandler.isRedirecting = true;
|
||||
ApiHandler.cancelAllRequests("Cancel All Requests - Unauthorized");
|
||||
// TODO: Navigate to login
|
||||
Get.offAllNamed('/login');
|
||||
}
|
||||
handler.reject(err);
|
||||
} finally {
|
||||
ApiHandler.isRefreshing = false;
|
||||
}
|
||||
} else if (err.type == DioExceptionType.cancel) {
|
||||
|
||||
handler.next(err);
|
||||
} else {
|
||||
handler.next(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_core/infrastructure/remote/interfaces/i_form_data.dart';
|
||||
|
||||
import 'interfaces/i_http_client.dart';
|
||||
|
||||
class DioRemote implements IHttpClient {
|
||||
String? baseUrl;
|
||||
late final Dio _dio;
|
||||
final List<Interceptor> interceptors;
|
||||
|
||||
DioRemote({this.baseUrl});
|
||||
DioRemote({this.baseUrl, this.interceptors = const []});
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
final dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
|
||||
dio.interceptors.addAll(interceptors);
|
||||
if (kDebugMode) {
|
||||
dio.interceptors.add(
|
||||
PrettyDioLogger(
|
||||
@@ -39,6 +39,7 @@ class DioRemote implements IHttpClient {
|
||||
queryParameters: queryParameters,
|
||||
options: Options(headers: headers),
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
cancelToken: ApiHandler.globalCancelToken
|
||||
);
|
||||
if (fromJsonList != null && response.data is List) {
|
||||
response.data = fromJsonList(response.data);
|
||||
@@ -68,6 +69,7 @@ class DioRemote implements IHttpClient {
|
||||
options: Options(headers: headers),
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
cancelToken: ApiHandler.globalCancelToken
|
||||
);
|
||||
|
||||
if (fromJson != null) {
|
||||
@@ -98,6 +100,7 @@ class DioRemote implements IHttpClient {
|
||||
options: Options(headers: headers),
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
cancelToken: ApiHandler.globalCancelToken
|
||||
);
|
||||
return DioResponse<T>(response);
|
||||
}
|
||||
@@ -114,6 +117,7 @@ class DioRemote implements IHttpClient {
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(headers: headers),
|
||||
cancelToken: ApiHandler.globalCancelToken
|
||||
);
|
||||
return DioResponse<T>(response);
|
||||
}
|
||||
@@ -127,6 +131,7 @@ class DioRemote implements IHttpClient {
|
||||
url,
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
cancelToken: ApiHandler.globalCancelToken
|
||||
);
|
||||
return DioResponse(response);
|
||||
}
|
||||
@@ -143,6 +148,7 @@ class DioRemote implements IHttpClient {
|
||||
data: (formData as DioFormData).raw,
|
||||
options: Options(headers: headers, contentType: 'multipart/form-data'),
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: ApiHandler.globalCancelToken
|
||||
);
|
||||
return DioResponse<T>(response);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../core.dart';
|
||||
|
||||
class ApiHandler {
|
||||
static bool _isRefreshing = false;
|
||||
static bool _isRedirecting = false;
|
||||
static CancelToken globalCancelToken = CancelToken();
|
||||
|
||||
static Future<void> reset() async {
|
||||
_isRefreshing = false;
|
||||
_isRedirecting = false;
|
||||
globalCancelToken = CancelToken();
|
||||
}
|
||||
|
||||
// متد جدید برای کنسل کردن همه درخواستها
|
||||
static void cancelAllRequests(String reason) {
|
||||
if (!globalCancelToken.isCancelled) {
|
||||
globalCancelToken.cancel(reason);
|
||||
}
|
||||
// CancelToken جدید بساز برای درخواستهای بعدی
|
||||
globalCancelToken = CancelToken();
|
||||
}
|
||||
|
||||
static bool get isRefreshing => _isRefreshing;
|
||||
static set isRefreshing(bool val) => _isRefreshing = val;
|
||||
|
||||
static bool get isRedirecting => _isRedirecting;
|
||||
static set isRedirecting(bool val) => _isRedirecting = val;
|
||||
}
|
||||
|
||||
typedef AppAsyncCallback<T> = Future<T> Function();
|
||||
typedef ErrorCallback = void Function(dynamic error, StackTrace? stackTrace);
|
||||
typedef VoidCallback = void Function();
|
||||
@@ -9,7 +34,6 @@ typedef VoidCallback = void Function();
|
||||
/// this is global safe call function
|
||||
/// A utility function to safely cal l an asynchronous function with error
|
||||
/// handling and optional loading, success, and error messages.
|
||||
///
|
||||
Future<void> gSafeCall<T>({
|
||||
required AppAsyncCallback<T> call,
|
||||
Function(T result)? onSuccess,
|
||||
@@ -20,62 +44,26 @@ Future<void> gSafeCall<T>({
|
||||
bool showSuccess = false,
|
||||
bool showToast = false,
|
||||
bool showSnackBar = false,
|
||||
bool retryOnAuthError = false,
|
||||
Function()? onTokenRefresh,
|
||||
Function()? onShowLoading,
|
||||
Function()? onHideLoading,
|
||||
Function()? onShowSuccessMessage,
|
||||
Function()? onShowErrorMessage,
|
||||
}) async {
|
||||
try {
|
||||
if (showLoading) {
|
||||
(onShowLoading ?? _defaultShowLoading)();
|
||||
}
|
||||
|
||||
if (showLoading) (onShowLoading ?? _defaultShowLoading)();
|
||||
final result = await call();
|
||||
|
||||
if (showSuccess) {
|
||||
(onShowSuccessMessage ?? _defaultShowSuccessMessage)();
|
||||
}
|
||||
|
||||
if (showSuccess) (onShowSuccessMessage ?? _defaultShowSuccessMessage)();
|
||||
onSuccess?.call(result);
|
||||
} catch (error, stackTrace) {
|
||||
if (retryOnAuthError && isTokenExpiredError(error)) {
|
||||
try {
|
||||
await onTokenRefresh?.call();
|
||||
final retryResult = await call();
|
||||
if (showSuccess) {
|
||||
(onShowSuccessMessage ?? _defaultShowSuccessMessage)();
|
||||
}
|
||||
onSuccess?.call(retryResult);
|
||||
return;
|
||||
} catch (retryError, retryStackTrace) {
|
||||
if (showError) {
|
||||
(onShowErrorMessage ?? _defaultShowErrorMessage)();
|
||||
}
|
||||
onError?.call(retryError, retryStackTrace);
|
||||
if (kDebugMode) {
|
||||
print('safeCall retry error: $retryError\n$retryStackTrace');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (showError) {
|
||||
(onShowErrorMessage ?? _defaultShowErrorMessage)();
|
||||
}
|
||||
onError?.call(error, stackTrace);
|
||||
if (kDebugMode) {
|
||||
print('safeCall error: $error\n$stackTrace');
|
||||
}
|
||||
}
|
||||
if (showError) (onShowErrorMessage ?? _defaultShowErrorMessage)();
|
||||
onError?.call(error, stackTrace);
|
||||
} finally {
|
||||
if (showLoading) {
|
||||
(onHideLoading ?? _defaultHideLoading)();
|
||||
}
|
||||
|
||||
if (showLoading) (onHideLoading ?? _defaultHideLoading)();
|
||||
onComplete?.call();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _defaultShowLoading() {
|
||||
// پیادهسازی پیشفرض
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user