Nested Navigator state restoration predictive back examples (#153723)

I've updated these two examples to support state restoration of the navigation stack and verified that they work with predictive back in the tests. This was motivated by a worry that users are not properly setting up their navigation and that our examples are misleading them in the name of simplicity.
This commit is contained in:
Justin McCandless 2024-08-21 20:40:09 -07:00 committed by GitHub
parent 203a19e82f
commit 420755dcfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 430 additions and 62 deletions

View File

@ -16,10 +16,35 @@ class NavigatorPopHandlerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
restorationScopeId: 'root',
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => _HomePage(),
'/nested_navigators': (BuildContext context) => const NestedNavigatorsPage(),
onGenerateRoute: (RouteSettings settings) {
return switch (settings.name) {
'/' => MaterialPageRoute<void>(
settings: const RouteSettings(
name: '/',
),
builder: (BuildContext context) {
return _HomePage();
},
),
'/nested_navigators' => MaterialPageRoute<void>(
settings: const RouteSettings(
name: '/nested_navigators',
),
builder: (BuildContext context) {
return const _NestedNavigatorsPage();
},
),
_ => MaterialPageRoute<void>(
settings: const RouteSettings(
name: 'unknown_page',
),
builder: (BuildContext context) {
return const _UnknownPage();
},
),
};
},
);
}
@ -43,7 +68,7 @@ class _HomePage extends StatelessWidget {
title: const Text('Nested Navigator route'),
subtitle: const Text('This route has another Navigator widget in addition to the one inside MaterialApp above.'),
onTap: () {
Navigator.of(context).pushNamed('/nested_navigators');
Navigator.of(context).restorablePushNamed('/nested_navigators');
},
),
],
@ -53,14 +78,14 @@ class _HomePage extends StatelessWidget {
}
}
class NestedNavigatorsPage extends StatefulWidget {
const NestedNavigatorsPage({super.key});
class _NestedNavigatorsPage extends StatefulWidget {
const _NestedNavigatorsPage();
@override
State<NestedNavigatorsPage> createState() => _NestedNavigatorsPageState();
State<_NestedNavigatorsPage> createState() => _NestedNavigatorsPageState();
}
class _NestedNavigatorsPageState extends State<NestedNavigatorsPage> {
class _NestedNavigatorsPageState extends State<_NestedNavigatorsPage> {
final GlobalKey<NavigatorState> _nestedNavigatorKey = GlobalKey<NavigatorState>();
@override
@ -71,36 +96,45 @@ class _NestedNavigatorsPageState extends State<NestedNavigatorsPage> {
},
child: Navigator(
key: _nestedNavigatorKey,
restorationScopeId: 'nested-navigator',
initialRoute: 'nested_navigators/one',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case 'nested_navigators/one':
final BuildContext rootContext = context;
return MaterialPageRoute<void>(
builder: (BuildContext context) => NestedNavigatorsPageOne(
onBack: () {
Navigator.of(rootContext).pop();
},
),
);
case 'nested_navigators/one/another_one':
return MaterialPageRoute<void>(
builder: (BuildContext context) => const NestedNavigatorsPageTwo(
),
);
default:
throw Exception('Invalid route: ${settings.name}');
}
final BuildContext rootContext = context;
return switch (settings.name) {
'nested_navigators/one' => MaterialPageRoute<void>(
settings: const RouteSettings(
name: 'nested_navigators/one',
),
builder: (BuildContext context) => _NestedNavigatorsPageOne(
onBack: () {
Navigator.of(rootContext).pop();
},
),
),
'nested_navigators/one/another_one' => MaterialPageRoute<void>(
settings: const RouteSettings(
name: 'nested_navigators/one',
),
builder: (BuildContext context) => const _NestedNavigatorsPageTwo(),
),
_ => MaterialPageRoute<void>(
settings: const RouteSettings(
name: 'unknown_page',
),
builder: (BuildContext context) {
return const _UnknownPage();
},
),
};
},
),
);
}
}
class NestedNavigatorsPageOne extends StatelessWidget {
const NestedNavigatorsPageOne({
class _NestedNavigatorsPageOne extends StatelessWidget {
const _NestedNavigatorsPageOne({
required this.onBack,
super.key,
});
final VoidCallback onBack;
@ -117,7 +151,7 @@ class NestedNavigatorsPageOne extends StatelessWidget {
const Text('A system back here returns to the home page.'),
TextButton(
onPressed: () {
Navigator.of(context).pushNamed('nested_navigators/one/another_one');
Navigator.of(context).restorablePushNamed('nested_navigators/one/another_one');
},
child: const Text('Go to another route in this nested Navigator'),
),
@ -135,10 +169,8 @@ class NestedNavigatorsPageOne extends StatelessWidget {
}
}
class NestedNavigatorsPageTwo extends StatelessWidget {
const NestedNavigatorsPageTwo({
super.key,
});
class _NestedNavigatorsPageTwo extends StatelessWidget {
const _NestedNavigatorsPageTwo();
@override
Widget build(BuildContext context) {
@ -162,3 +194,22 @@ class NestedNavigatorsPageTwo extends StatelessWidget {
);
}
}
class _UnknownPage extends StatelessWidget {
const _UnknownPage();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey.withBlue(180),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('404'),
],
),
),
);
}
}

View File

@ -16,7 +16,15 @@ enum _Tab {
// Each tab has two possible pages.
enum _TabPage {
home,
one,
one;
static _TabPage? fromName(String? name) {
return switch (name) {
'home' => _TabPage.home,
'one' => _TabPage.one,
_ => null,
};
}
}
typedef _TabPageCallback = void Function(List<_TabPage> pages);
@ -29,10 +37,27 @@ class NavigatorPopHandlerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/home',
routes: <String, WidgetBuilder>{
'/home': (BuildContext context) => const _BottomNavPage(
),
initialRoute: '/',
restorationScopeId: 'root',
onGenerateRoute: (RouteSettings settings) {
return switch (settings.name) {
'/' => MaterialPageRoute<void>(
settings: const RouteSettings(
name: '/',
),
builder: (BuildContext context) {
return const _BottomNavPage();
},
),
_ => MaterialPageRoute<void>(
settings: const RouteSettings(
name: 'unknown_page',
),
builder: (BuildContext context) {
return const _UnknownPage();
},
),
};
},
);
}
@ -45,16 +70,16 @@ class _BottomNavPage extends StatefulWidget {
State<_BottomNavPage> createState() => _BottomNavPageState();
}
class _BottomNavPageState extends State<_BottomNavPage> {
_Tab _tab = _Tab.home;
class _BottomNavPageState extends State<_BottomNavPage> with RestorationMixin {
final _RestorableTab _restorableTab = _RestorableTab();
final GlobalKey _tabHomeKey = GlobalKey();
final GlobalKey _tabOneKey = GlobalKey();
final GlobalKey _tabTwoKey = GlobalKey();
List<_TabPage> _tabHomePages = <_TabPage>[_TabPage.home];
List<_TabPage> _tabOnePages = <_TabPage>[_TabPage.home];
List<_TabPage> _tabTwoPages = <_TabPage>[_TabPage.home];
final _RestorableTabPageList _restorableTabHomePages = _RestorableTabPageList();
final _RestorableTabPageList _restorableTabOnePages = _RestorableTabPageList();
final _RestorableTabPageList _restorableTabTwoPages = _RestorableTabPageList();
BottomNavigationBarItem _itemForPage(_Tab page) {
switch (page) {
@ -83,10 +108,10 @@ class _BottomNavPageState extends State<_BottomNavPage> {
key: _tabHomeKey,
title: 'Home Tab',
color: Colors.grey,
pages: _tabHomePages,
onChangedPages: (List<_TabPage> pages) {
pages: _restorableTabHomePages.value,
onChangePages: (List<_TabPage> pages) {
setState(() {
_tabHomePages = pages;
_restorableTabHomePages.value = pages;
});
},
);
@ -95,10 +120,10 @@ class _BottomNavPageState extends State<_BottomNavPage> {
key: _tabOneKey,
title: 'Tab One',
color: Colors.amber,
pages: _tabOnePages,
onChangedPages: (List<_TabPage> pages) {
pages: _restorableTabOnePages.value,
onChangePages: (List<_TabPage> pages) {
setState(() {
_tabOnePages = pages;
_restorableTabOnePages.value = pages;
});
},
);
@ -107,10 +132,10 @@ class _BottomNavPageState extends State<_BottomNavPage> {
key: _tabTwoKey,
title: 'Tab Two',
color: Colors.blueGrey,
pages: _tabTwoPages,
onChangedPages: (List<_TabPage> pages) {
pages: _restorableTabTwoPages.value,
onChangePages: (List<_TabPage> pages) {
setState(() {
_tabTwoPages = pages;
_restorableTabTwoPages.value = pages;
});
},
);
@ -119,19 +144,43 @@ class _BottomNavPageState extends State<_BottomNavPage> {
void _onItemTapped(int index) {
setState(() {
_tab = _Tab.values.elementAt(index);
_restorableTab.value = _Tab.values.elementAt(index);
});
}
// Begin RestorationMixin.
@override
String? get restorationId => 'bottom-nav-page';
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_restorableTab, 'tab');
registerForRestoration(_restorableTabHomePages, 'tab-home-pages');
registerForRestoration(_restorableTabOnePages, 'tab-one-pages');
registerForRestoration(_restorableTabTwoPages, 'tab-two-pages');
}
/// End RestorationMixin.
@override
void dispose() {
_restorableTab.dispose();
_restorableTabHomePages.dispose();
_restorableTabOnePages.dispose();
_restorableTabTwoPages.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _getPage(_tab),
child: _getPage(_restorableTab.value),
),
bottomNavigationBar: BottomNavigationBar(
items: _Tab.values.map(_itemForPage).toList(),
currentIndex: _Tab.values.indexOf(_tab),
currentIndex: _Tab.values.indexOf(_restorableTab.value),
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
@ -143,13 +192,13 @@ class _BottomNavTab extends StatefulWidget {
const _BottomNavTab({
super.key,
required this.color,
required this.onChangedPages,
required this.onChangePages,
required this.pages,
required this.title,
});
final Color color;
final _TabPageCallback onChangedPages;
final _TabPageCallback onChangePages;
final List<_TabPage> pages;
final String title;
@ -168,22 +217,33 @@ class _BottomNavTabState extends State<_BottomNavTab> {
},
child: Navigator(
key: _navigatorKey,
restorationScopeId: 'nested-navigator-${widget.title}',
onDidRemovePage: (Page<Object?> page) {
widget.onChangedPages(<_TabPage>[
final _TabPage? tabPage = _TabPage.fromName(page.name);
if (tabPage == null) {
return;
}
final List<_TabPage> nextPages = <_TabPage>[
...widget.pages,
]..removeLast());
]..remove(tabPage);
if (nextPages.length < widget.pages.length) {
widget.onChangePages(nextPages);
}
},
pages: widget.pages.map((_TabPage page) {
switch (page) {
case _TabPage.home:
return MaterialPage<void>(
restorationId: _TabPage.home.toString(),
name: 'home',
child: _LinksPage(
title: 'Bottom nav - tab ${widget.title} - route $page',
backgroundColor: widget.color,
buttons: <Widget>[
TextButton(
onPressed: () {
widget.onChangedPages(<_TabPage>[
assert(!widget.pages.contains(_TabPage.one));
widget.onChangePages(<_TabPage>[
...widget.pages,
_TabPage.one,
]);
@ -195,13 +255,15 @@ class _BottomNavTabState extends State<_BottomNavTab> {
);
case _TabPage.one:
return MaterialPage<void>(
restorationId: _TabPage.one.toString(),
name: 'one',
child: _LinksPage(
backgroundColor: widget.color,
title: 'Bottom nav - tab ${widget.title} - route $page',
buttons: <Widget>[
TextButton(
onPressed: () {
widget.onChangedPages(<_TabPage>[
widget.onChangePages(<_TabPage>[
...widget.pages,
]..removeLast());
},
@ -244,3 +306,79 @@ class _LinksPage extends StatelessWidget {
);
}
}
class _UnknownPage extends StatelessWidget {
const _UnknownPage();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey.withBlue(180),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('404'),
],
),
),
);
}
}
class _RestorableTab extends RestorableValue<_Tab> {
@override
_Tab createDefaultValue() => _Tab.home;
@override
void didUpdateValue(_Tab? oldValue) {
if (oldValue == null || oldValue != value) {
notifyListeners();
}
}
@override
_Tab fromPrimitives(Object? data) {
if (data != null) {
final String tabString = data as String;
return _Tab.values.firstWhere((_Tab tab) => tabString == tab.name);
}
return _Tab.home;
}
@override
Object toPrimitives() {
return value.name;
}
}
class _RestorableTabPageList extends RestorableValue<List<_TabPage>> {
@override
List<_TabPage> createDefaultValue() => <_TabPage>[_TabPage.home];
@override
void didUpdateValue(List<_TabPage>? oldValue) {
if (oldValue == null || oldValue != value) {
notifyListeners();
}
}
@override
List<_TabPage> fromPrimitives(Object? data) {
if (data != null) {
final String dataString = data as String;
final List<String> listOfStrings = dataString.split(',');
return listOfStrings.map((String tabPageName) {
return _TabPage.values.firstWhere((_TabPage tabPage) => tabPageName == tabPage.name);
}).toList();
}
return <_TabPage>[];
}
@override
Object toPrimitives() {
return value
.map((_TabPage tabPage) => tabPage.name)
.join(',');
}
}

View File

@ -2,12 +2,40 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_api_samples/widgets/navigator_pop_handler/navigator_pop_handler.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
import '../navigator_utils.dart';
void main() {
bool? lastFrameworkHandlesBack;
setUp(() async {
lastFrameworkHandlesBack = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
if (methodCall.method == 'SystemNavigator.setFrameworkHandlesBack') {
expect(methodCall.arguments, isA<bool>());
lastFrameworkHandlesBack = methodCall.arguments as bool;
}
return;
});
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
'flutter/lifecycle',
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
(ByteData? data) {},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null);
});
testWidgets('Can go back with system back gesture', (WidgetTester tester) async {
await tester.pumpWidget(
const example.NavigatorPopHandlerApp(),
@ -16,6 +44,9 @@ void main() {
expect(find.text('Nested Navigators Example'), findsOneWidget);
expect(find.text('Nested Navigators Page One'), findsNothing);
expect(find.text('Nested Navigators Page Two'), findsNothing);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
await tester.tap(find.text('Nested Navigator route'));
await tester.pumpAndSettle();
@ -23,6 +54,9 @@ void main() {
expect(find.text('Nested Navigators Example'), findsNothing);
expect(find.text('Nested Navigators Page One'), findsOneWidget);
expect(find.text('Nested Navigators Page Two'), findsNothing);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
await tester.tap(find.text('Go to another route in this nested Navigator'));
await tester.pumpAndSettle();
@ -30,6 +64,9 @@ void main() {
expect(find.text('Nested Navigators Example'), findsNothing);
expect(find.text('Nested Navigators Page One'), findsNothing);
expect(find.text('Nested Navigators Page Two'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
await simulateSystemBack();
await tester.pumpAndSettle();
@ -37,6 +74,9 @@ void main() {
expect(find.text('Nested Navigators Example'), findsNothing);
expect(find.text('Nested Navigators Page One'), findsOneWidget);
expect(find.text('Nested Navigators Page Two'), findsNothing);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
await simulateSystemBack();
await tester.pumpAndSettle();
@ -44,5 +84,50 @@ void main() {
expect(find.text('Nested Navigators Example'), findsOneWidget);
expect(find.text('Nested Navigators Page One'), findsNothing);
expect(find.text('Nested Navigators Page Two'), findsNothing);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
});
testWidgets('restoring the app preserves the navigation stack', (WidgetTester tester) async {
await tester.pumpWidget(
const example.NavigatorPopHandlerApp(),
);
expect(find.text('Nested Navigators Example'), findsOneWidget);
expect(find.text('Nested Navigators Page One'), findsNothing);
expect(find.text('Nested Navigators Page Two'), findsNothing);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
await tester.tap(find.text('Nested Navigator route'));
await tester.pumpAndSettle();
expect(find.text('Nested Navigators Example'), findsNothing);
expect(find.text('Nested Navigators Page One'), findsOneWidget);
expect(find.text('Nested Navigators Page Two'), findsNothing);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
await tester.tap(find.text('Go to another route in this nested Navigator'));
await tester.pumpAndSettle();
expect(find.text('Nested Navigators Example'), findsNothing);
expect(find.text('Nested Navigators Page One'), findsNothing);
expect(find.text('Nested Navigators Page Two'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
await tester.restartAndRestore();
expect(find.text('Nested Navigators Example'), findsNothing);
expect(find.text('Nested Navigators Page One'), findsNothing);
expect(find.text('Nested Navigators Page Two'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
});
}

View File

@ -2,37 +2,131 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_api_samples/widgets/navigator_pop_handler/navigator_pop_handler.1.dart' as example;
import 'package:flutter_test/flutter_test.dart';
import '../navigator_utils.dart';
void main() {
bool? lastFrameworkHandlesBack;
setUp(() async {
lastFrameworkHandlesBack = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
if (methodCall.method == 'SystemNavigator.setFrameworkHandlesBack') {
expect(methodCall.arguments, isA<bool>());
lastFrameworkHandlesBack = methodCall.arguments as bool;
}
return;
});
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
'flutter/lifecycle',
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
(ByteData? data) {},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null);
});
testWidgets("System back gesture operates on current tab's nested Navigator", (WidgetTester tester) async {
await tester.pumpWidget(
const example.NavigatorPopHandlerApp(),
);
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.home'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
// Go to the next route in this tab.
await tester.tap(find.text('Go to another route in this nested Navigator'));
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.one'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
// Go to another tab.
await tester.tap(find.text('Go to One'));
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Tab One - route _TabPage.home'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
// Return to the home tab. The navigation state is preserved.
await tester.tap(find.text('Go to Home'));
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.one'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
// A back pops the navigation stack of the current tab's nested Navigator.
await simulateSystemBack();
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.home'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
});
testWidgets('restoring the app preserves the navigation stack', (WidgetTester tester) async {
await tester.pumpWidget(
const example.NavigatorPopHandlerApp(),
);
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.home'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
// Go to the next route in this tab.
await tester.tap(find.text('Go to another route in this nested Navigator'));
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.one'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
// Go to another tab.
await tester.tap(find.text('Go to One'));
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Tab One - route _TabPage.home'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
await tester.restartAndRestore();
expect(find.text('Bottom nav - tab Tab One - route _TabPage.home'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
// Return to the home tab. The navigation state is preserved.
await tester.tap(find.text('Go to Home'));
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.one'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
// A back pops the navigation stack of the current tab's nested Navigator.
await simulateSystemBack();
await tester.pumpAndSettle();
expect(find.text('Bottom nav - tab Home Tab - route _TabPage.home'), findsOneWidget);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
});
}