mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
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:
parent
203a19e82f
commit
420755dcfa
@ -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'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(',');
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user