Skip to content

Commit 0c8953d

Browse files
gnpricePIG208
authored andcommitted
log: Support reportErrorToUser* helpers.
The imports in `lib/log.dart` are for the code references in the reportErrorToUser* helpers' dart doc. The motivation of having these set of helpers, rather than using showErrorDialog and showSnackBar directly, is that we make displaying errors more accessible in non-widget code.
1 parent 6f9623e commit 0c8953d

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

lib/log.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import 'package:flutter/material.dart';
2+
3+
import 'widgets/app.dart';
14

25
/// Whether [debugLog] should do anything.
36
///
@@ -30,3 +33,43 @@ bool debugLog(String message) {
3033
}());
3134
return true;
3235
}
36+
37+
/// Display an error message in a [SnackBar].
38+
///
39+
/// This shows a [SnackBar] containing the message after [ZulipApp] becomes ready,
40+
/// otherwise logs it to the console.
41+
// This gets set in [ZulipApp].
42+
void Function(String message) get reportErrorToUserInSnackBar => (message) {
43+
_debugLastReportedError = message;
44+
_reportErrorToUserInSnackBar(message);
45+
};
46+
void Function(String message) _reportErrorToUserInSnackBar = _defaultReportErrorToUser;
47+
set reportErrorToUserInSnackBar(void Function(String message) value) => _reportErrorToUserInSnackBar = value;
48+
49+
/// Display an error message in a dialog.
50+
///
51+
/// This shows a dialog containing the message after [ZulipApp] becomes ready,
52+
/// otherwise logs it to the console.
53+
// This gets set in [ZulipApp].
54+
void Function(String message) get reportErrorToUserInDialog => (message) {
55+
_debugLastReportedError = message;
56+
_reportErrorToUserInDialog(message);
57+
};
58+
void Function(String message) _reportErrorToUserInDialog = _defaultReportErrorToUser;
59+
set reportErrorToUserInDialog(void Function(String message) value) => _reportErrorToUserInDialog = value;
60+
61+
String? get debugLastReportedError => _debugLastReportedError;
62+
String? _debugLastReportedError;
63+
String? debugTakeLastReportedError() {
64+
final result = _debugLastReportedError;
65+
_debugLastReportedError = null;
66+
return result;
67+
}
68+
69+
void _defaultReportErrorToUser(String message) {
70+
// If this callback is still in place, then the app's widget tree
71+
// hasn't mounted yet even as far as the [Navigator].
72+
// So there's not much we can do to tell the user;
73+
// just log, in case the user is actually a developer watching the console.
74+
assert(debugLog(message));
75+
}

lib/widgets/app.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import 'package:flutter/material.dart';
55
import 'package:flutter/scheduler.dart';
66
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
77

8+
import '../log.dart';
89
import '../model/localizations.dart';
910
import '../model/narrow.dart';
1011
import 'about_zulip.dart';
1112
import 'app_bar.dart';
13+
import 'dialog.dart';
1214
import 'inbox.dart';
1315
import 'login.dart';
1416
import 'message_list.dart';
@@ -92,9 +94,21 @@ class ZulipApp extends StatefulWidget {
9294
/// Useful in tests.
9395
final List<NavigatorObserver>? navigatorObservers;
9496

97+
static void _reportErrorToUserInSnackBar(String message) {
98+
scaffoldMessenger?.showSnackBar(SnackBar(content: Text(message)));
99+
}
100+
101+
static void _reportErrorToUserInDialog(String message) {
102+
final context = navigatorKey.currentContext;
103+
if (context == null) return;
104+
showErrorDialog(context: context, title: 'Error', message: message);
105+
}
106+
95107
void _declareReady() {
96108
assert(navigatorKey.currentContext != null);
97109
_ready.value = true;
110+
reportErrorToUserInSnackBar = _reportErrorToUserInSnackBar;
111+
reportErrorToUserInDialog = _reportErrorToUserInDialog;
98112
}
99113

100114
@override

test/widgets/app_test.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:checks/checks.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:zulip/log.dart';
45
import 'package:zulip/model/database.dart';
56
import 'package:zulip/widgets/app.dart';
67
import 'package:zulip/widgets/inbox.dart';
@@ -10,6 +11,7 @@ import '../example_data.dart' as eg;
1011
import '../flutter_checks.dart';
1112
import '../model/binding.dart';
1213
import '../test_navigation.dart';
14+
import 'dialog_checks.dart';
1315
import 'page_checks.dart';
1416
import 'test_app.dart';
1517

@@ -169,5 +171,52 @@ void main() {
169171
check(ZulipApp.scaffoldMessenger).isNotNull();
170172
check(ZulipApp.ready).value.isTrue();
171173
});
174+
175+
testWidgets('reportErrorToUserBriefly shows snack bar', (tester) async {
176+
addTearDown(testBinding.reset);
177+
await tester.pumpWidget(const ZulipApp());
178+
179+
final finder = find.descendant(
180+
of: find.byType(SnackBar),
181+
matching: find.text('test error message'));
182+
183+
// Prior to app startup, reportErrorToUserBriefly only logs.
184+
reportErrorToUserInSnackBar('test error message');
185+
check(ZulipApp.ready).value.isFalse();
186+
await tester.pump();
187+
check(debugTakeLastReportedError()).equals('test error message');
188+
check(finder.evaluate()).isEmpty();
189+
190+
check(ZulipApp.ready).value.isTrue();
191+
// After app startup, reportErrorToUserBriefly displays a snack bar.
192+
reportErrorToUserInSnackBar('test error message');
193+
await tester.pump();
194+
check(finder.evaluate()).single;
195+
});
196+
197+
testWidgets('reportErrorToUserInDialog shows dialog', (tester) async {
198+
addTearDown(testBinding.reset);
199+
await tester.pumpWidget(const ZulipApp());
200+
201+
final finder = find.descendant(
202+
of: find.byType(AlertDialog),
203+
matching: find.text('test error message'));
204+
205+
// Prior to app startup, reportErrorToUserInDialog only logs.
206+
reportErrorToUserInDialog('test error message');
207+
check(ZulipApp.ready).value.isFalse();
208+
await tester.pump();
209+
check(debugTakeLastReportedError()).equals('test error message');
210+
check(finder.evaluate()).isEmpty();
211+
212+
check(ZulipApp.ready).value.isTrue();
213+
// After app startup, reportErrorToUserInDialog displays a dialog.
214+
reportErrorToUserInDialog('test error message');
215+
await tester.pump();
216+
check(finder.evaluate()).single;
217+
// Check the title too.
218+
checkErrorDialog(tester,
219+
expectedTitle: 'Error', expectedMessage: 'test error message');
220+
});
172221
});
173222
}

0 commit comments

Comments
 (0)