Skip to content

Commit e1ac92f

Browse files
committed
app: Maintain that the navigator stack is never empty
Signed-off-by: Zixuan James Li <[email protected]>
1 parent e460628 commit e1ac92f

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

lib/widgets/app.dart

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,11 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
223223
theme: themeData,
224224

225225
navigatorKey: ZulipApp.navigatorKey,
226-
navigatorObservers: widget.navigatorObservers ?? const [],
226+
navigatorObservers: [
227+
if (widget.navigatorObservers != null)
228+
...widget.navigatorObservers!,
229+
_PreventEmptyStack(),
230+
],
227231
builder: (BuildContext context, Widget? child) {
228232
if (!ZulipApp.ready.value) {
229233
SchedulerBinding.instance.addPostFrameCallback(
@@ -246,6 +250,33 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
246250
}
247251
}
248252

253+
/// Pushes a route whenever the observed navigator stack becomes empty.
254+
class _PreventEmptyStack extends NavigatorObserver {
255+
void _pushRouteIfEmptyStack() async {
256+
final navigator = await ZulipApp.navigator;
257+
bool isEmptyStack = true;
258+
// TODO: find a better way to inspect the navigator stack
259+
navigator.popUntil((route) {
260+
isEmptyStack = false;
261+
return true; // never actually pops
262+
});
263+
if (isEmptyStack) {
264+
unawaited(navigator.push(
265+
MaterialWidgetRoute(page: const ChooseAccountPage())));
266+
}
267+
}
268+
269+
@override
270+
void didRemove(Route<void> route, Route<void>? previousRoute) async {
271+
_pushRouteIfEmptyStack();
272+
}
273+
274+
@override
275+
void didPop(Route<void> route, Route<void>? previousRoute) async {
276+
_pushRouteIfEmptyStack();
277+
}
278+
}
279+
249280
class ChooseAccountPage extends StatelessWidget {
250281
const ChooseAccountPage({super.key});
251282

test/widgets/app_test.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:checks/checks.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_test/flutter_test.dart';
66
import 'package:zulip/log.dart';
7+
import 'package:zulip/model/actions.dart';
78
import 'package:zulip/model/database.dart';
89
import 'package:zulip/widgets/app.dart';
910
import 'package:zulip/widgets/home.dart';
@@ -57,6 +58,42 @@ void main() {
5758
});
5859
});
5960

61+
group('_PreventEmptyStack', () {
62+
late List<Route<void>> pushedRoutes;
63+
late List<Route<void>> removedRoutes;
64+
65+
Future<void> prepare(WidgetTester tester) async {
66+
addTearDown(testBinding.reset);
67+
68+
pushedRoutes = [];
69+
removedRoutes = [];
70+
final testNavObserver = TestNavigatorObserver();
71+
testNavObserver.onPushed = (route, prevRoute) => pushedRoutes.add(route);
72+
testNavObserver.onRemoved = (route, prevRoute) => removedRoutes.add(route);
73+
74+
await tester.pumpWidget(ZulipApp(navigatorObservers: [testNavObserver]));
75+
await tester.pump(); // start to load account
76+
check(pushedRoutes).single.isA<WidgetRoute>().page.isA<HomePage>();
77+
pushedRoutes.clear();
78+
}
79+
80+
testWidgets('push route when removing last route on stack', (tester) async {
81+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
82+
await prepare(tester);
83+
// The navigator stack should contain only a home page route.
84+
85+
// Log out, causing the home page to be removed from the stack.
86+
final future = logOutAccount(testBinding.globalStore, eg.selfAccount.id);
87+
await tester.pump(TestGlobalStore.removeAccountDuration);
88+
await future;
89+
check(testBinding.globalStore.takeDoRemoveAccountCalls())
90+
.single.equals(eg.selfAccount.id);
91+
// The choose-account page should appear.
92+
check(removedRoutes).single.isA<WidgetRoute>().page.isA<HomePage>();
93+
check(pushedRoutes).single.isA<WidgetRoute>().page.isA<ChooseAccountPage>();
94+
});
95+
});
96+
6097
group('ChooseAccountPage', () {
6198
Future<void> setupChooseAccountPage(WidgetTester tester, {
6299
required List<Account> accounts,

0 commit comments

Comments
 (0)