Separate Exception Handler class from main [skip ci]

This commit is contained in:
OmarHatem 2023-02-01 17:37:18 +02:00
parent 08ff37e23c
commit 15e7395fe9
2 changed files with 108 additions and 104 deletions

View file

@ -1,22 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_mailer/flutter_mailer.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -50,21 +40,18 @@ import 'package:cake_wallet/wallet_type_utils.dart';
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>(); final rootKey = GlobalKey<RootState>();
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
bool hasError = false;
Future<void> main() async { Future<void> main() async {
await runZonedGuarded(() async { await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (errorDetails) { FlutterError.onError = ExceptionHandler.onError;
_onError(errorDetails);
};
/// A callback that is invoked when an unhandled error occurs in the root /// A callback that is invoked when an unhandled error occurs in the root
/// isolate. /// isolate.
PlatformDispatcher.instance.onError = (error, stack) { PlatformDispatcher.instance.onError = (error, stack) {
_onError(FlutterErrorDetails(exception: error, stack: stack)); ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
return true; return true;
}; };
@ -155,97 +142,10 @@ Future<void> main() async {
initialMigrationVersion: 19); initialMigrationVersion: 19);
runApp(App()); runApp(App());
}, (error, stackTrace) async { }, (error, stackTrace) async {
_onError(FlutterErrorDetails(exception: error, stack: stackTrace)); ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
}); });
} }
void _saveException(String? error, StackTrace? stackTrace) async {
final appDocDir = await getApplicationDocumentsDirectory();
final file = File('${appDocDir.path}/error.txt');
final exception = {
"${DateTime.now()}": {
"Error": error,
"StackTrace": stackTrace.toString(),
}
};
const String separator =
'''\n\n==========================================================
==========================================================\n\n''';
await file.writeAsString(
jsonEncode(exception) + separator,
mode: FileMode.append,
);
}
void _sendExceptionFile() async {
try {
final appDocDir = await getApplicationDocumentsDirectory();
final file = File('${appDocDir.path}/error.txt');
final MailOptions mailOptions = MailOptions(
subject: 'Mobile App Issue',
recipients: ['support@cakewallet.com'],
attachments: [file.path],
);
final result = await FlutterMailer.send(mailOptions);
// Clear file content if the error was sent or saved.
// On android we can't know if it was sent or saved
if (result.name == MailerResponse.sent.name ||
result.name == MailerResponse.saved.name ||
result.name == MailerResponse.android.name) {
file.writeAsString("", mode: FileMode.write);
}
} catch (e, s) {
_saveException(e.toString(), s);
}
}
void _onError(FlutterErrorDetails errorDetails) {
// Add Exception for user's network connection issues to not be reported
if (errorDetails.exception.toString().contains("Software caused connection abort")) {
return;
}
_saveException(errorDetails.exception.toString(), errorDetails.stack);
if (hasError) {
return;
}
hasError = true;
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) async {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (context) {
return AlertWithTwoActions(
isDividerExist: true,
alertTitle: S.of(context).error,
alertContent: S.of(context).error_dialog_content,
rightButtonText: S.of(context).send,
leftButtonText: S.of(context).do_not_send,
actionRightButton: () {
Navigator.of(context).pop();
_sendExceptionFile();
},
actionLeftButton: () {
Navigator.of(context).pop();
},
);
},
);
hasError = false;
},
);
}
Future<void> initialSetup( Future<void> initialSetup(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences,
required Box<Node> nodes, required Box<Node> nodes,

View file

@ -0,0 +1,104 @@
import 'dart:convert';
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mailer/flutter_mailer.dart';
import 'package:path_provider/path_provider.dart';
class ExceptionHandler {
static bool _hasError = false;
static void _saveException(String? error, StackTrace? stackTrace) async {
final appDocDir = await getApplicationDocumentsDirectory();
final file = File('${appDocDir.path}/error.txt');
final exception = {
"${DateTime.now()}": {
"Error": error,
"StackTrace": stackTrace.toString(),
}
};
const String separator = '''\n\n==========================================================
==========================================================\n\n''';
await file.writeAsString(
jsonEncode(exception) + separator,
mode: FileMode.append,
);
}
static void _sendExceptionFile() async {
try {
final appDocDir = await getApplicationDocumentsDirectory();
final file = File('${appDocDir.path}/error.txt');
final MailOptions mailOptions = MailOptions(
subject: 'Mobile App Issue',
recipients: ['support@cakewallet.com'],
attachments: [file.path],
);
final result = await FlutterMailer.send(mailOptions);
// Clear file content if the error was sent or saved.
// On android we can't know if it was sent or saved
if (result.name == MailerResponse.sent.name ||
result.name == MailerResponse.saved.name ||
result.name == MailerResponse.android.name) {
file.writeAsString("", mode: FileMode.write);
}
} catch (e, s) {
_saveException(e.toString(), s);
}
}
static void onError(FlutterErrorDetails errorDetails) {
if (_isErrorFromUser(errorDetails.exception.toString())) {
return;
}
_saveException(errorDetails.exception.toString(), errorDetails.stack);
if (_hasError) {
return;
}
_hasError = true;
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) async {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (context) {
return AlertWithTwoActions(
isDividerExist: true,
alertTitle: S.of(context).error,
alertContent: S.of(context).error_dialog_content,
rightButtonText: S.of(context).send,
leftButtonText: S.of(context).do_not_send,
actionRightButton: () {
Navigator.of(context).pop();
_sendExceptionFile();
},
actionLeftButton: () {
Navigator.of(context).pop();
},
);
},
);
_hasError = false;
},
);
}
/// User related errors to be added as exceptions here to not report
static bool _isErrorFromUser(String error) {
return error.contains("Software caused connection abort"); // User connection issue
}
}