fix : add cancel Token
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import 'package:rasadyar_auth/data/common/constant.dart';
|
|
||||||
import 'package:rasadyar_auth/data/common/dio_error_handler.dart';
|
import 'package:rasadyar_auth/data/common/dio_error_handler.dart';
|
||||||
|
import 'package:rasadyar_auth/data/models/response/auth/auth_response_model.dart';
|
||||||
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
|
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
|
||||||
|
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
|
||||||
import 'package:rasadyar_core/core.dart';
|
import 'package:rasadyar_core/core.dart';
|
||||||
|
|
||||||
import '../common/dio_manager.dart';
|
import '../common/dio_manager.dart';
|
||||||
@@ -9,8 +10,27 @@ GetIt diAuth = GetIt.instance;
|
|||||||
|
|
||||||
Future<void> setupAuthDI() async {
|
Future<void> setupAuthDI() async {
|
||||||
diAuth.registerLazySingleton(() => DioRemoteManager());
|
diAuth.registerLazySingleton(() => DioRemoteManager());
|
||||||
diAuth.registerLazySingleton<DioRemote>(() => DioRemote());
|
diAuth.registerLazySingleton<AppInterceptor>(
|
||||||
|
() => AppInterceptor(
|
||||||
|
refreshTokenCallback: () async {
|
||||||
|
var tokenService = Get.find<TokenStorageService>();
|
||||||
|
final authRepo = diAuth.get<AuthRepositoryImpl>();
|
||||||
|
|
||||||
|
final refreshToken = tokenService.refreshToken.value;
|
||||||
|
if (refreshToken == null) return null;
|
||||||
|
|
||||||
|
final result = await authRepo.loginWithRefreshToken(authRequest: {"refresh_token": refreshToken});
|
||||||
|
|
||||||
|
if (result is AuthResponseModel) {
|
||||||
|
await tokenService.saveAccessToken(result.access!);
|
||||||
|
return result.access;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
diAuth.registerLazySingleton<DioRemote>(() => DioRemote(interceptors: [diAuth.get<AppInterceptor>()]));
|
||||||
|
|
||||||
final dioRemote = diAuth.get<DioRemote>();
|
final dioRemote = diAuth.get<DioRemote>();
|
||||||
await dioRemote.init();
|
await dioRemote.init();
|
||||||
@@ -19,11 +39,11 @@ Future<void> setupAuthDI() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> newSetupAuthDI(String newUrl) async {
|
Future<void> newSetupAuthDI(String newUrl) async {
|
||||||
diAuth.registerLazySingleton<DioRemote>(() => DioRemote(baseUrl: newUrl),instanceName: 'newRemote');
|
diAuth.registerLazySingleton<DioRemote>(
|
||||||
|
() => DioRemote(baseUrl: newUrl, interceptors: [diAuth.get<AppInterceptor>()]),
|
||||||
|
instanceName: 'newRemote',
|
||||||
|
);
|
||||||
final dioRemote = diAuth.get<DioRemote>(instanceName: 'newRemote');
|
final dioRemote = diAuth.get<DioRemote>(instanceName: 'newRemote');
|
||||||
await dioRemote.init();
|
await dioRemote.init();
|
||||||
diAuth.registerSingleton<AuthRepositoryImpl>(
|
diAuth.registerSingleton<AuthRepositoryImpl>(AuthRepositoryImpl(dioRemote), instanceName: 'newUrl');
|
||||||
AuthRepositoryImpl(dioRemote),
|
|
||||||
instanceName: 'newUrl',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:rasadyar_auth/auth.dart';
|
import 'package:rasadyar_auth/auth.dart';
|
||||||
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
|
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
|
||||||
import 'package:rasadyar_core/core.dart';
|
import 'package:rasadyar_core/core.dart';
|
||||||
|
|
||||||
import '../models/response/auth/auth_response_model.dart';
|
import '../models/response/auth/auth_response_model.dart';
|
||||||
import '../services/token_storage_service.dart';
|
import '../services/token_storage_service.dart';
|
||||||
|
|
||||||
Future<void> safeCall<T>({
|
Future<void> safeCall<T>({
|
||||||
required AppAsyncCallback<T> call,
|
required AppAsyncCallback<T> call,
|
||||||
Function(T result)? onSuccess,
|
Function(T result)? onSuccess,
|
||||||
@@ -21,9 +19,6 @@ Future<void> safeCall<T>({
|
|||||||
Function()? onShowSuccessMessage,
|
Function()? onShowSuccessMessage,
|
||||||
Function()? onShowErrorMessage,
|
Function()? onShowErrorMessage,
|
||||||
}) {
|
}) {
|
||||||
final authRepository = diAuth.get<AuthRepositoryImpl>();
|
|
||||||
TokenStorageService tokenStorageService = Get.find<TokenStorageService>();
|
|
||||||
|
|
||||||
return gSafeCall(
|
return gSafeCall(
|
||||||
call: call,
|
call: call,
|
||||||
onSuccess: onSuccess,
|
onSuccess: onSuccess,
|
||||||
@@ -38,24 +33,6 @@ Future<void> safeCall<T>({
|
|||||||
onHideLoading: onHideLoading,
|
onHideLoading: onHideLoading,
|
||||||
onShowSuccessMessage: onShowSuccessMessage,
|
onShowSuccessMessage: onShowSuccessMessage,
|
||||||
onShowErrorMessage: onShowErrorMessage,
|
onShowErrorMessage: onShowErrorMessage,
|
||||||
retryOnAuthError: true,
|
|
||||||
onTokenRefresh: () {
|
|
||||||
var token = tokenStorageService.refreshToken.value;
|
|
||||||
authRepository
|
|
||||||
.loginWithRefreshToken(authRequest: {"refresh_token": token})
|
|
||||||
.then((value) async {
|
|
||||||
if (value is AuthResponseModel) {
|
|
||||||
await tokenStorageService.saveAccessToken(value.access!);
|
|
||||||
} else {
|
|
||||||
throw Exception("Failed to refresh token");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catchError((error) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print('Error during token refresh: $error');
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import '../models/request/create_steward_free_bar/create_steward_free_bar.dart';
|
|||||||
abstract class ChickenRepository {
|
abstract class ChickenRepository {
|
||||||
Future<List<InventoryModel>?> getInventory({required String token});
|
Future<List<InventoryModel>?> getInventory({required String token});
|
||||||
|
|
||||||
Future<KillHouseDistributionInfo?> getIKillHouseDistributionInfo({required String token});
|
Future<KillHouseDistributionInfo?> getKillHouseDistributionInfo({required String token});
|
||||||
|
|
||||||
Future<BarInformation?> getGeneralBarInformation({required String token, Map<String, dynamic>? queryParameters});
|
Future<BarInformation?> getGeneralBarInformation({required String token, Map<String, dynamic>? queryParameters});
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class ChickenRepositoryImpl implements ChickenRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<KillHouseDistributionInfo?> getIKillHouseDistributionInfo({required String token}) async {
|
Future<KillHouseDistributionInfo?> getKillHouseDistributionInfo({required String token}) async {
|
||||||
var res = await _httpClient.get(
|
var res = await _httpClient.get(
|
||||||
'/kill-house-distribution-info/?role=Steward',
|
'/kill-house-distribution-info/?role=Steward',
|
||||||
headers: {'Authorization': 'Bearer $token'},
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class HomeLogic extends GetxController {
|
|||||||
|
|
||||||
Future<void> getDistributionInformation() async {
|
Future<void> getDistributionInformation() async {
|
||||||
await safeCall<KillHouseDistributionInfo?>(
|
await safeCall<KillHouseDistributionInfo?>(
|
||||||
call: () async => await rootLogic.chickenRepository.getIKillHouseDistributionInfo(token: rootLogic.tokenService.accessToken.value!),
|
call: () async => await rootLogic.chickenRepository.getKillHouseDistributionInfo(token: rootLogic.tokenService.accessToken.value!),
|
||||||
onSuccess: (result) {
|
onSuccess: (result) {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
killHouseDistributionInfo.value = result;
|
killHouseDistributionInfo.value = result;
|
||||||
|
|||||||
@@ -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/utils/utils.dart';
|
||||||
export 'package:rasadyar_core/presentation/widget/widget.dart';
|
export 'package:rasadyar_core/presentation/widget/widget.dart';
|
||||||
|
|
||||||
export 'infrastructure/local/hive_local_storage.dart';
|
//infrastructure
|
||||||
//network
|
export 'infrastructure/infrastructure.dart';
|
||||||
export 'infrastructure/remote/dio_form_data.dart';
|
|
||||||
export 'infrastructure/remote/dio_remote.dart';
|
|
||||||
export 'infrastructure/remote/dio_response.dart';
|
|
||||||
export 'injection/di.dart';
|
|
||||||
|
|
||||||
///image picker
|
///image picker
|
||||||
export 'package:image_picker/image_picker.dart';
|
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:flutter/foundation.dart';
|
||||||
import 'package:rasadyar_core/core.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 {
|
class DioRemote implements IHttpClient {
|
||||||
String? baseUrl;
|
String? baseUrl;
|
||||||
late final Dio _dio;
|
late final Dio _dio;
|
||||||
|
final List<Interceptor> interceptors;
|
||||||
|
|
||||||
DioRemote({this.baseUrl});
|
DioRemote({this.baseUrl, this.interceptors = const []});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
final dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
|
final dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
|
||||||
|
dio.interceptors.addAll(interceptors);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
dio.interceptors.add(
|
dio.interceptors.add(
|
||||||
PrettyDioLogger(
|
PrettyDioLogger(
|
||||||
@@ -39,6 +39,7 @@ class DioRemote implements IHttpClient {
|
|||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
options: Options(headers: headers),
|
options: Options(headers: headers),
|
||||||
onReceiveProgress: onReceiveProgress,
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: ApiHandler.globalCancelToken
|
||||||
);
|
);
|
||||||
if (fromJsonList != null && response.data is List) {
|
if (fromJsonList != null && response.data is List) {
|
||||||
response.data = fromJsonList(response.data);
|
response.data = fromJsonList(response.data);
|
||||||
@@ -68,6 +69,7 @@ class DioRemote implements IHttpClient {
|
|||||||
options: Options(headers: headers),
|
options: Options(headers: headers),
|
||||||
onSendProgress: onSendProgress,
|
onSendProgress: onSendProgress,
|
||||||
onReceiveProgress: onReceiveProgress,
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: ApiHandler.globalCancelToken
|
||||||
);
|
);
|
||||||
|
|
||||||
if (fromJson != null) {
|
if (fromJson != null) {
|
||||||
@@ -98,6 +100,7 @@ class DioRemote implements IHttpClient {
|
|||||||
options: Options(headers: headers),
|
options: Options(headers: headers),
|
||||||
onSendProgress: onSendProgress,
|
onSendProgress: onSendProgress,
|
||||||
onReceiveProgress: onReceiveProgress,
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: ApiHandler.globalCancelToken
|
||||||
);
|
);
|
||||||
return DioResponse<T>(response);
|
return DioResponse<T>(response);
|
||||||
}
|
}
|
||||||
@@ -114,6 +117,7 @@ class DioRemote implements IHttpClient {
|
|||||||
data: data,
|
data: data,
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
options: Options(headers: headers),
|
options: Options(headers: headers),
|
||||||
|
cancelToken: ApiHandler.globalCancelToken
|
||||||
);
|
);
|
||||||
return DioResponse<T>(response);
|
return DioResponse<T>(response);
|
||||||
}
|
}
|
||||||
@@ -127,6 +131,7 @@ class DioRemote implements IHttpClient {
|
|||||||
url,
|
url,
|
||||||
options: Options(responseType: ResponseType.bytes),
|
options: Options(responseType: ResponseType.bytes),
|
||||||
onReceiveProgress: onReceiveProgress,
|
onReceiveProgress: onReceiveProgress,
|
||||||
|
cancelToken: ApiHandler.globalCancelToken
|
||||||
);
|
);
|
||||||
return DioResponse(response);
|
return DioResponse(response);
|
||||||
}
|
}
|
||||||
@@ -143,6 +148,7 @@ class DioRemote implements IHttpClient {
|
|||||||
data: (formData as DioFormData).raw,
|
data: (formData as DioFormData).raw,
|
||||||
options: Options(headers: headers, contentType: 'multipart/form-data'),
|
options: Options(headers: headers, contentType: 'multipart/form-data'),
|
||||||
onSendProgress: onSendProgress,
|
onSendProgress: onSendProgress,
|
||||||
|
cancelToken: ApiHandler.globalCancelToken
|
||||||
);
|
);
|
||||||
return DioResponse<T>(response);
|
return DioResponse<T>(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,32 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import '../../core.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 AppAsyncCallback<T> = Future<T> Function();
|
||||||
typedef ErrorCallback = void Function(dynamic error, StackTrace? stackTrace);
|
typedef ErrorCallback = void Function(dynamic error, StackTrace? stackTrace);
|
||||||
typedef VoidCallback = void Function();
|
typedef VoidCallback = void Function();
|
||||||
@@ -9,7 +34,6 @@ typedef VoidCallback = void Function();
|
|||||||
/// this is global safe call function
|
/// this is global safe call function
|
||||||
/// A utility function to safely cal l an asynchronous function with error
|
/// A utility function to safely cal l an asynchronous function with error
|
||||||
/// handling and optional loading, success, and error messages.
|
/// handling and optional loading, success, and error messages.
|
||||||
///
|
|
||||||
Future<void> gSafeCall<T>({
|
Future<void> gSafeCall<T>({
|
||||||
required AppAsyncCallback<T> call,
|
required AppAsyncCallback<T> call,
|
||||||
Function(T result)? onSuccess,
|
Function(T result)? onSuccess,
|
||||||
@@ -20,62 +44,26 @@ Future<void> gSafeCall<T>({
|
|||||||
bool showSuccess = false,
|
bool showSuccess = false,
|
||||||
bool showToast = false,
|
bool showToast = false,
|
||||||
bool showSnackBar = false,
|
bool showSnackBar = false,
|
||||||
bool retryOnAuthError = false,
|
|
||||||
Function()? onTokenRefresh,
|
|
||||||
Function()? onShowLoading,
|
Function()? onShowLoading,
|
||||||
Function()? onHideLoading,
|
Function()? onHideLoading,
|
||||||
Function()? onShowSuccessMessage,
|
Function()? onShowSuccessMessage,
|
||||||
Function()? onShowErrorMessage,
|
Function()? onShowErrorMessage,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
if (showLoading) {
|
if (showLoading) (onShowLoading ?? _defaultShowLoading)();
|
||||||
(onShowLoading ?? _defaultShowLoading)();
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await call();
|
final result = await call();
|
||||||
|
if (showSuccess) (onShowSuccessMessage ?? _defaultShowSuccessMessage)();
|
||||||
if (showSuccess) {
|
|
||||||
(onShowSuccessMessage ?? _defaultShowSuccessMessage)();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccess?.call(result);
|
onSuccess?.call(result);
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
if (retryOnAuthError && isTokenExpiredError(error)) {
|
if (showError) (onShowErrorMessage ?? _defaultShowErrorMessage)();
|
||||||
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);
|
onError?.call(error, stackTrace);
|
||||||
if (kDebugMode) {
|
|
||||||
print('safeCall error: $error\n$stackTrace');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (showLoading) {
|
if (showLoading) (onHideLoading ?? _defaultHideLoading)();
|
||||||
(onHideLoading ?? _defaultHideLoading)();
|
|
||||||
}
|
|
||||||
|
|
||||||
onComplete?.call();
|
onComplete?.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void _defaultShowLoading() {
|
void _defaultShowLoading() {
|
||||||
// پیادهسازی پیشفرض
|
// پیادهسازی پیشفرض
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user