Last active
January 24, 2024 15:43
-
-
Save diefferson/cd28b97c59b3bef1efbef35e376a6713 to your computer and use it in GitHub Desktop.
Flutter error Handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'dart:io'; | |
import 'package:dio/dio.dart'; | |
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; | |
import 'package:flutter/services.dart'; | |
import 'package:noodle_commons/src/domain/exception/error_handler.dart'; | |
import 'package:noodle_commons/src/utils/json_utils.dart'; | |
import 'package:uuid/uuid.dart'; | |
import 'dio/dio_client.dart'; | |
class AppRest { | |
final Dio _dio; | |
final ErrorHandler? _errorHandler; | |
AppRest( | |
String baseUrl, { | |
CacheOptions? cacheOptions, | |
ErrorHandler? errorHandler, | |
List<Interceptor>? interceptors, | |
}) : _errorHandler = errorHandler, | |
_dio = DioClient.getClient( | |
baseUrl: baseUrl, | |
cacheOptions: cacheOptions, | |
interceptors: interceptors ?? [], | |
); | |
static String getCacheKeyBuilder(RequestOptions request, {String name = ''}) { | |
return const Uuid().v5(Uuid.NAMESPACE_URL, | |
'${request.uri.toString()}/$name/${request.headers.getString(HttpHeaders.authorizationHeader)}'); | |
} | |
void updateBaseUrl(String baseUrl) { | |
_dio.options.baseUrl = baseUrl; | |
} | |
Future<T> get<T>( | |
String path, { | |
Map<String, dynamic>? queryParameters, | |
Options? options, | |
bool mock = false, | |
String? mockAsset, | |
}) async { | |
if (mock) { | |
return await mockResponse(mockAsset); | |
} | |
try { | |
final response = await _dio.get( | |
path, | |
queryParameters: queryParameters, | |
options: options, | |
); | |
return response.data; | |
} catch (exception) { | |
final e = _errorHandler?.handle(exception); | |
if (e != null) { | |
throw e; | |
} else { | |
rethrow; | |
} | |
} | |
} | |
Future<T> post<T>( | |
String path, { | |
data, | |
Map<String, dynamic>? queryParameters, | |
Options? options, | |
bool mock = false, | |
String? mockAsset, | |
}) async { | |
if (mock) { | |
return await mockResponse(mockAsset); | |
} | |
try { | |
final response = await _dio.post( | |
path, | |
data: data, | |
queryParameters: queryParameters, | |
options: options, | |
); | |
return response.data; | |
} catch (exception) { | |
final e = _errorHandler?.handle(exception); | |
if (e != null) { | |
throw e; | |
} else { | |
rethrow; | |
} | |
} | |
} | |
Future<T> put<T>( | |
String path, { | |
data, | |
Map<String, dynamic>? queryParameters, | |
Options? options, | |
bool mock = false, | |
String? mockAsset, | |
}) async { | |
if (mock) { | |
return await mockResponse(mockAsset); | |
} | |
try { | |
final response = await _dio.put( | |
path, | |
data: data, | |
queryParameters: queryParameters, | |
options: options, | |
); | |
return response.data; | |
} catch (exception) { | |
final e = _errorHandler?.handle(exception); | |
if (e != null) { | |
throw e; | |
} else { | |
rethrow; | |
} | |
} | |
} | |
Future<T> delete<T>( | |
String path, { | |
data, | |
Map<String, dynamic>? queryParameters, | |
Options? options, | |
bool mock = false, | |
String? mockAsset, | |
}) async { | |
if (mock) { | |
return await mockResponse(mockAsset); | |
} | |
try { | |
final response = await _dio.delete( | |
path, | |
data: data, | |
queryParameters: queryParameters, | |
options: options, | |
); | |
return response.data; | |
} catch (exception) { | |
final e = _errorHandler?.handle(exception); | |
if (e != null) { | |
throw e; | |
} else { | |
rethrow; | |
} | |
} | |
} | |
Future<T> patch<T>( | |
String path, { | |
data, | |
Map<String, dynamic>? queryParameters, | |
Options? options, | |
bool mock = false, | |
String? mockAsset, | |
}) async { | |
if (mock) { | |
return await mockResponse(mockAsset); | |
} | |
try { | |
final response = await _dio.patch( | |
path, | |
data: data, | |
queryParameters: queryParameters, | |
options: options, | |
); | |
return response.data; | |
} catch (exception) { | |
final e = _errorHandler?.handle(exception); | |
if (e != null) { | |
throw e; | |
} else { | |
rethrow; | |
} | |
} | |
} | |
Future<dynamic> mockResponse(String? mockAsset) async { | |
assert(mockAsset != null, 'Mock asset should not null to mock mode'); | |
try { | |
final response = jsonDecode(await rootBundle.loadString(mockAsset!)); | |
return response; | |
} catch (exception) { | |
final e = _errorHandler?.handle(exception); | |
if (e != null) { | |
throw e; | |
} else { | |
rethrow; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:firebase_performance/firebase_performance.dart'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:noodle_commons/src/analytics/noodle_analytics.dart'; | |
import 'package:noodle_commons/src/domain/error_service.dart'; | |
import 'package:noodle_commons/src/domain/loading_service.dart'; | |
import 'package:stark/stark.dart'; | |
import 'exception/error_handler.dart'; | |
import 'exception/noodle_exception.dart'; | |
/// Abstract class for a UseCase | |
abstract class UseCase<Type, Params> { | |
@protected | |
Future<Type> run(Params params); | |
String get metricName => runtimeType.toString(); | |
static const _successMetric = 'success'; | |
static const _errorCode = 'errorCode'; | |
Function(Type) _onSuccess = (_) {}; | |
Function(NoodleException) _onError = (_) {}; | |
final ErrorHandler _errorHandler = Stark.get(); | |
final NoodleAnalytics _noodleAnalytics = Stark.get(); | |
final FirebasePerformance _monitoring = FirebasePerformance.instance; | |
UseCase<Type, Params> execute({ | |
Params? params, | |
bool withLoading = false, | |
bool withError = false, | |
}) { | |
_tryExecute( | |
params ?? None() as Params, | |
withLoading: withLoading, | |
withError: withError, | |
); | |
return this; | |
} | |
@protected | |
NoodleException handleError(NoodleException exception) { | |
return exception; | |
} | |
UseCase<Type, Params> onError(Function(NoodleException) action) { | |
_onError = action; | |
return this; | |
} | |
UseCase<Type, Params> onSuccess(Function(Type) action) { | |
_onSuccess = action; | |
return this; | |
} | |
Future<Type> asFuture({ | |
Type Function(NoodleException)? errorParser, | |
}) { | |
final Completer<Type> completer = Completer(); | |
onSuccess((data) { | |
completer.complete(data); | |
}).onError((e) { | |
if (errorParser != null) { | |
completer.complete(errorParser(e)); | |
} else { | |
completer.completeError(e); | |
} | |
}); | |
return completer.future; | |
} | |
Stream<Type> asStream({ | |
Type Function(NoodleException)? errorParser, | |
}) { | |
return Stream.fromFuture(asFuture(errorParser: errorParser)); | |
} | |
Future _tryExecute( | |
Params params, { | |
bool withLoading = false, | |
bool withError = false, | |
}) async { | |
Trace trace = _monitoring.newTrace(metricName); | |
trace.start(); | |
try { | |
if (withLoading) { | |
LoadingService.showLoading(); | |
} | |
final result = await run(params); | |
if (withLoading) { | |
LoadingService.dismissLoading(); | |
} | |
trace.setMetric(_successMetric, 1); | |
trace.stop(); | |
_onSuccess(result); | |
} on Exception catch (e, stacktrace) { | |
if (withLoading) { | |
LoadingService.dismissLoading(); | |
} | |
trace.setMetric(_successMetric, 0); | |
final error = handleError(_errorHandler.handle(e)); | |
_noodleAnalytics.logException(error, stacktrace); | |
if (withError) { | |
ErrorService.showException(error); | |
} | |
trace.setMetric( | |
_errorCode, | |
int.tryParse(error.errorCode) ?? 0, | |
); | |
trace.stop(); | |
_onError(error); | |
} catch (e, stacktrace) { | |
if (withLoading) { | |
LoadingService.dismissLoading(); | |
} | |
trace.setMetric(_successMetric, 0); | |
final error = handleError(_errorHandler.handle(e)); | |
_noodleAnalytics.logException(error, stacktrace); | |
if (withError) { | |
ErrorService.showException(error); | |
} | |
trace.setMetric( | |
_errorCode, | |
int.tryParse(error.errorCode) ?? 0, | |
); | |
trace.stop(); | |
_onError(error); | |
} | |
} | |
} | |
class None {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:noodle_commons/src/domain/exception/noodle_exception.dart'; | |
abstract class ErrorHandler { | |
NoodleException handle(exception); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'package:dio/dio.dart'; | |
import 'package:noodle_commons/src/domain/exception/error_handler.dart'; | |
import 'package:noodle_commons/src/domain/exception/noodle_exception.dart'; | |
import 'package:flutter/services.dart'; | |
import 'package:noodle_validators/noodle_validators.dart'; | |
import 'network_exception.dart'; | |
class NetworkErrorHandler implements ErrorHandler { | |
@override | |
NoodleException handle(exception) { | |
if (exception is NoodleException) { | |
return exception; | |
} | |
if (exception is DioException) { | |
return _handleDioError(exception); | |
} | |
return UnexpectedException(cause: exception); | |
} | |
NoodleException _handleDioError(DioException exception) { | |
if (exception.error is NoodleException) { | |
return exception.error as NoodleException; | |
} else if (exception.error is PlatformException) { | |
return _handlePlatformException(exception.error as PlatformException); | |
} | |
if (exception.response != null) { | |
return _handleResponseException(exception, exception.response!); | |
} | |
return UnexpectedException(cause: exception); | |
} | |
NoodleException _handleResponseException( | |
DioException exception, | |
Response response, | |
) { | |
switch (exception.response?.statusCode) { | |
case 301: | |
case 302: | |
case 401: | |
return UnauthorizedException( | |
errorCode: _getResponseCode(response) ?? | |
response.statusCode?.toString() ?? | |
'', | |
message: _getResponseMessage(response) ?? exception.message ?? "", | |
cause: exception, | |
); | |
case 404: | |
return NotFoundException(); | |
default: | |
return ServerException( | |
errorCode: _getResponseCode(response) ?? | |
response.statusCode?.toString() ?? | |
'', | |
message: _getResponseMessage(response) ?? exception.message ?? "", | |
cause: exception, | |
); | |
} | |
} | |
String? _getResponseCode(Response response) { | |
try { | |
return response.data['code'].toString(); | |
} catch (_) { | |
return null; | |
} | |
} | |
String? _getResponseMessage(Response response) { | |
try { | |
return (response.data['message'] as String).toCamelCase(); | |
} catch (_) { | |
return null; | |
} | |
} | |
NoodleException _handlePlatformException(PlatformException exception) { | |
return UnexpectedException(cause: exception); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:base/base.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:module_banking/module_banking.dart'; | |
import 'package:module_banking/src/domain/model/transfer_exception.dart'; | |
mixin TransferPageActions on FlowState<TransferPage, TransferPagePresenter> { | |
@override | |
void showError(NoodleException exception) { | |
if (mounted) { | |
if (exception is TransferInsufficientFundsException) { | |
_showError( | |
context: context, | |
title: BankingStrings.of(context).insufficientBalance, | |
message: BankingStrings.of(context).insufficientBalanceText, | |
); | |
} else if (exception is TransferRejectedException) { | |
_showError( | |
context: context, | |
title: BankingStrings.of(context).transactionProblems, | |
message: BankingStrings.of(context).transferRejected, | |
finish: true, | |
); | |
} else if (exception is TransferGetDetailsException) { | |
_showError( | |
context: context, | |
title: BankingStrings.of(context).transactionProblems, | |
message: BankingStrings.of(context).errorCheckTransaction, | |
finish: true, | |
); | |
} else { | |
_showError( | |
context: context, | |
title: AppStrings.of(context).simpleOps, | |
message: BankingStrings.of(context).transferError, | |
); | |
} | |
} | |
} | |
Future _showError({ | |
required BuildContext context, | |
required String title, | |
required String message, | |
bool finish = false | |
}) async { | |
await NoodleErrorBottomSheet.show( | |
context: context, | |
title: title, | |
message: message, | |
); | |
if (finish) { | |
if (mounted) { | |
Navigator.of(context).pop(false); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:base/base.dart'; | |
import 'package:module_banking/module_banking.dart'; | |
import 'package:module_banking/src/domain/interactors/transfer_use_case.dart'; | |
import 'package:module_banking/src/domain/model/transfer_exception.dart'; | |
class TransferPresenter extends FlowPresenter<TransferPageActions> { | |
TransferPresenter(this._transferUseCase); | |
final TransferUseCase _transferUseCase; | |
double amount = 0.0; | |
final _bankAccount = BehaviorSubject<BankAccount>(); | |
Stream<BankAccount> get bankAccount => _bankAccount.stream; | |
String transferMessage = ''; | |
void doTransfer() { | |
final transfer = Transfer( | |
amount: amount, | |
type: _bankAccount.value.transferType, | |
message: transferMessage, | |
bankAccount: _bankAccount.value, | |
date: DateTime.now(), | |
); | |
_transferUseCase | |
.execute( | |
withLoading: true, | |
params: TransferUseCaseParams(transfer) | |
).onSuccess((data) { | |
nextPage(); | |
}).onError(flowView.showError); | |
} | |
void setBankAccount(BankAccount bankAccount) { | |
_bankAccount.safeAdd(bankAccount); | |
} | |
void setAmount(String amount) { | |
this.amount = amount.toDouble(); | |
nextPage(); | |
} | |
@override | |
void dispose() { | |
_bankAccount.safeClose(); | |
super.dispose(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:base/base.dart'; | |
import 'package:module_banking/module_banking.dart'; | |
import 'package:module_banking/src/data/transfer_repository.dart'; | |
import 'package:module_banking/src/domain/model/transfer_exception.dart'; | |
class TransferUseCase extends UseCase<String, TransferUseCaseParams> { | |
TransferUseCase( | |
this._repository, | |
this._balanceUpdate | |
); | |
final TransferRepository _repository; | |
final BalanceUpdate _balanceUpdate; | |
@override | |
Future<String> run(TransferUseCaseParams params) async { | |
final transactionId = await _repository.transfer(params.transfer); | |
_balanceUpdate.update(); | |
return transactionId; | |
} | |
@override | |
NoodleException handleError(NoodleException exception) { | |
if (exception.errorCode == insufficientFundsCode) { | |
return TransferInsufficientFundsException(); | |
} else { | |
return TransferException(); | |
} | |
} | |
} | |
class TransferUseCaseParams { | |
TransferUseCaseParams(this.transfer); | |
final Transfer transfer; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment