Skip to content

Instantly share code, notes, and snippets.

@diefferson
Last active January 24, 2024 15:43
Show Gist options
  • Save diefferson/cd28b97c59b3bef1efbef35e376a6713 to your computer and use it in GitHub Desktop.
Save diefferson/cd28b97c59b3bef1efbef35e376a6713 to your computer and use it in GitHub Desktop.
Flutter error Handling
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;
}
}
}
}
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 {}
import 'package:noodle_commons/src/domain/exception/noodle_exception.dart';
abstract class ErrorHandler {
NoodleException handle(exception);
}
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);
}
}
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);
}
}
}
}
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();
}
}
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