Commit 4b7a1912 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add NavigatorState.pushAndRemoveUntil (#10026)

Fixes #9989
parent db84df23
......@@ -968,6 +968,66 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
assert(() { _debugLocked = false; return true; });
}
/// Push the given route and then remove all the previous routes until the
/// `predicate` returns true.
///
/// The predicate may be applied to the same route more than once if
/// [Route.willHandlePopInternally] is true.
///
/// To remove routes until a route with a certain name, use the
/// [RoutePredicate] returned from [ModalRoute.withName].
///
/// To remove all the routes before the pushed route, use a [RoutePredicate]
/// that always returns false.
Future<dynamic> pushAndRemoveUntil(Route<dynamic> newRoute, RoutePredicate predicate) {
assert(!_debugLocked);
assert(() { _debugLocked = true; return true; });
final List<Route<dynamic>> removedRoutes = <Route<dynamic>>[];
while (_history.isNotEmpty && !predicate(_history.last)) {
final Route<dynamic> removedRoute = _history.removeLast();
assert(removedRoute != null && removedRoute._navigator == this);
assert(removedRoute.overlayEntries.isNotEmpty);
removedRoutes.add(removedRoute);
}
assert(newRoute._navigator == null);
assert(newRoute.overlayEntries.isEmpty);
setState(() {
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
newRoute._navigator = this;
newRoute.install(_currentOverlayEntry);
_history.add(newRoute);
newRoute.didPush().whenCompleteOrCancel(() {
if (mounted) {
for (Route<dynamic> route in removedRoutes)
route.dispose();
}
});
newRoute.didChangeNext(null);
if (oldRoute != null)
oldRoute.didChangeNext(newRoute);
for (NavigatorObserver observer in widget.observers)
observer.didPush(newRoute, oldRoute);
});
assert(() { _debugLocked = false; return true; });
_cancelActivePointers();
return newRoute.popped;
}
/// Push the route with the given name and then remove all the previous routes
/// until the `predicate` returns true.
///
/// The predicate may be applied to the same route more than once if
/// [Route.willHandlePopInternally] is true.
///
/// To remove routes until a route with a certain name, use the
/// [RoutePredicate] returned from [ModalRoute.withName].
///
/// To remove all the routes before the pushed route, use a [RoutePredicate]
/// that always returns false.
Future<dynamic> pushNamedAndRemoveUntil(String routeName, RoutePredicate predicate) {
return pushAndRemoveUntil(_routeNamed(routeName), predicate);
}
/// Tries to pop the current route, first giving the active route the chance
/// to veto the operation using [Route.willPop]. This method is typically
/// called instead of [pop] when the user uses a back button. For example on
......
......@@ -6,8 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
void main() {
testWidgets('Back during pushReplacement',
(WidgetTester tester) async {
testWidgets('Back during pushReplacement', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp(
home: const Material(child: const Text("home")),
routes: <String, WidgetBuilder> {
......@@ -39,4 +38,43 @@ void main() {
expect(find.text('b'), findsNothing);
expect(find.text('home'), findsOneWidget);
});
testWidgets('pushAndRemoveUntil', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp(
home: const Material(child: const Text("home")),
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => const Material(child: const Text("a")),
'/b': (BuildContext context) => const Material(child: const Text("b")),
},
));
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pushNamed('/a');
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsOneWidget);
expect(find.text('a', skipOffstage: false), findsOneWidget);
expect(find.text('b', skipOffstage: false), findsNothing);
navigator.pushNamedAndRemoveUntil('/b', (Route<dynamic> route) => false);
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsNothing);
expect(find.text('a', skipOffstage: false), findsNothing);
expect(find.text('b', skipOffstage: false), findsOneWidget);
navigator.pushNamed('/');
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsOneWidget);
expect(find.text('a', skipOffstage: false), findsNothing);
expect(find.text('b', skipOffstage: false), findsOneWidget);
navigator.pushNamedAndRemoveUntil('/a', ModalRoute.withName('/b'));
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsNothing);
expect(find.text('a', skipOffstage: false), findsOneWidget);
expect(find.text('b', skipOffstage: false), findsOneWidget);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment