Skip to content

Commit

Permalink
feat(#678): add top-level error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
tamslo committed Dec 27, 2023
1 parent d340c01 commit 4554921
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 20 deletions.
29 changes: 16 additions & 13 deletions app/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@ class PharMeApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return LifecycleObserver(
return ErrorHandler(
appRouter: _appRouter,
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
routeInformationParser: _appRouter.defaultRouteParser(),
routerDelegate: _appRouter.delegate(deepLinkBuilder: getInitialRoute),
theme: PharMeTheme.light,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [Locale('en', '')],
child: LifecycleObserver(
appRouter: _appRouter,
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
routeInformationParser: _appRouter.defaultRouteParser(),
routerDelegate: _appRouter.delegate(deepLinkBuilder: getInitialRoute),
theme: PharMeTheme.light,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [Locale('en', '')],
),
),
);
}
Expand Down
7 changes: 5 additions & 2 deletions app/lib/common/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ Uri keycloakUrl([String slug = '']) =>
// Note that sending emails will not work on the iPhone Simulator since it does
// not have any email application installed.
String _mailContact = '[email protected]';
Future<void> sendEmail({String subject = ''}) async {
Future<void> sendEmail({String subject = '', String body = ''}) async {
await launchUrl(Uri(
scheme: 'mailto',
path: _mailContact,
queryParameters: {'subject': subject}));
queryParameters: {
'subject': subject,
'body': Uri.encodeComponent(body),
}));
}

final cpicMaxCacheTime = Duration(days: 90);
Expand Down
2 changes: 2 additions & 0 deletions app/lib/common/routing/router.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '../../drug/module.dart';
import '../../drug_selection/module.dart';
import '../../error/module.dart';
import '../../faq/module.dart';
import '../../login/module.dart';
import '../../main/module.dart';
Expand All @@ -19,6 +20,7 @@ class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
drugSelectionRoute(),
errorRoute(),
loginRoute(),
mainRoute(
children: [
Expand Down
43 changes: 43 additions & 0 deletions app/lib/common/widgets/error_handler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import '../module.dart';

class ErrorHandler extends StatefulWidget {
const ErrorHandler({
super.key,
required this.appRouter,
required this.child,
});

final AppRouter appRouter;
final Widget child;

@override
State<StatefulWidget> createState() => ErrorHandlerState();
}

class ErrorHandlerState extends State<ErrorHandler> {
Future<void> _handleError({
required Object exception,
StackTrace? stackTrace,
}) async {
debugPrint(exception.toString());
debugPrintStack(stackTrace: stackTrace);
await widget.appRouter.push(ErrorRoute(error: exception.toString()));
}

@override
void initState() {
FlutterError.onError = (details) {
_handleError(exception: details.exception, stackTrace: details.stack);
};
WidgetsBinding.instance.platformDispatcher.onError =
(exception, stackTrace) {
_handleError(exception: exception, stackTrace: stackTrace);
return true;
};
super.initState();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
2 changes: 2 additions & 0 deletions app/lib/common/widgets/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ export 'dialog_content_text.dart';
export 'dialog_wrapper.dart';
export 'drug_list/builder.dart';
export 'drug_list/cubit.dart';
export 'error_handler.dart';
export 'filter_menu.dart';
export 'full_width_button.dart';
export 'headings.dart';
export 'indicators.dart';
export 'lifecycle_observer.dart';
Expand Down
24 changes: 21 additions & 3 deletions app/lib/common/widgets/pharme_logo_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ class PharMeLogoPage extends StatelessWidget {
const PharMeLogoPage({
super.key,
this.child,
this.greyscale = false,
});

final Widget? child;
final bool greyscale;

@override
Widget build(BuildContext context) {
Expand All @@ -17,11 +19,23 @@ class PharMeLogoPage extends StatelessWidget {
Expanded(
child: Container(
alignment: Alignment.center,
child: SvgPicture.asset(
'assets/images/logo.svg',
child: greyscale
? ColorFiltered(
colorFilter: ColorFilter.mode(
PharMeTheme.backgroundColor,
BlendMode.softLight,
),
child: ColorFiltered(
colorFilter: ColorFilter.mode(
PharMeTheme.backgroundColor,
BlendMode.saturation,
),
child: _buildLogo(context),
),
)
: _buildLogo(context),
),
),
),
Container(
alignment: Alignment.center,
child: child ?? SizedBox.shrink(),
Expand All @@ -30,4 +44,8 @@ class PharMeLogoPage extends StatelessWidget {
),
);
}

Widget _buildLogo(BuildContext context) {
return SvgPicture.asset('assets/images/logo.svg');
}
}
1 change: 0 additions & 1 deletion app/lib/drug_selection/pages/drug_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import '../../common/models/metadata.dart';
import '../../common/module.dart' hide MetaData;
import '../../common/widgets/drug_list/drug_items/drug_checkbox_list.dart';
import '../../common/widgets/drug_search.dart';
import '../../common/widgets/full_width_button.dart';
import '../cubit.dart';

@RoutePage()
Expand Down
10 changes: 10 additions & 0 deletions app/lib/error/module.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import '../common/module.dart';

// For generated routes
export 'pages/error.dart';

CustomRoute errorRoute() => CustomRoute(
path: '/error',
page: ErrorRoute.page,
transitionsBuilder: TransitionsBuilders.fadeIn,
);
81 changes: 81 additions & 0 deletions app/lib/error/pages/error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import 'dart:io';

import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';

import '../../common/module.dart';

@RoutePage()
class ErrorPage extends StatelessWidget {
const ErrorPage({required this.error, super.key});

final String error;

@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
child: PharMeLogoPage(
greyscale: true,
child: Column(
children: [
Text(
context.l10n.error_title,
style: PharMeTheme.textTheme.headlineMedium,
),
SizedBox(height: PharMeTheme.mediumSpace),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: PharMeTheme.textTheme.bodyLarge,
children: [
TextSpan(
text: context.l10n.error_uncaught_message_first_part,
),
TextSpan(
text: context.l10n.error_uncaught_message_bold_part,
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
SizedBox(height: PharMeTheme.mediumSpace),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: PharMeTheme.textTheme.bodyLarge,
children: [
TextSpan(text: context.l10n.error_uncaught_message_contact),
TextSpan(
text: context.l10n.error_contact_link_text,
style: TextStyle(
color: PharMeTheme.secondaryColor,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()..onTap =
() {
sendEmail(
subject: context.l10n.error_mail_subject,
body: context.l10n.error_mail_body(error),
);
},
),
TextSpan(
text: context.l10n.error_uncaught_message_after_link,
),
],
),
),
SizedBox(height: PharMeTheme.mediumSpace),
FullWidthButton(context.l10n.error_close_app, () async {
if (Platform.isIOS) {
exit(0);
}
await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}),
],
),
),
);
}
}
18 changes: 18 additions & 0 deletions app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@
"action_continue": "Continue",
"action_back_to_app": "Back to app",

"error_title": "Something went wrong",
"error_uncaught_message_first_part": "PharMe has encountered an unknown error. ",
"error_uncaught_message_bold_part": "Please close the app and open it again.",
"error_uncaught_message_contact": "The error has been logged for our technical staff; however, please ",
"error_contact_link_text": "contact us",
"error_uncaught_message_after_link": " if this problem persists.",
"error_close_app": "Close app",
"error_mail_subject": "Unknown PharMe Error Report",
"error_mail_body": "Error: {error} (please keep this line)\n\n<Please describe what happened right before the error occurred.>",
"@error_mail_body": {
"placeholders": {
"error": {
"type": "String",
"example": "Exception"
}
}
},

"auth_welcome": "Welcome to PharMe",
"auth_choose_lab": "Please select your data provider",
"auth_sign_in": "Get data",
Expand Down
1 change: 0 additions & 1 deletion app/lib/login/pages/login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:provider/provider.dart';

import '../../../common/module.dart';
import '../../common/widgets/full_width_button.dart';
import '../cubit.dart';
import '../models/lab.dart';

Expand Down

0 comments on commit 4554921

Please sign in to comment.