Handle back during pushReplacement (#10016)

Previously, we would strand the old route in the overlay even though it had
been removed from the history.
This commit is contained in:
Adam Barth 2017-05-11 19:33:37 -07:00 committed by GitHub
parent b9f4e5dac7
commit bd56c79767
6 changed files with 64 additions and 12 deletions

View file

@ -102,7 +102,10 @@ class TabController extends ChangeNotifier {
notifyListeners(); // Because the value of indexIsChanging may have changed.
_animationController
.animateTo(_index.toDouble(), duration: duration, curve: curve)
.orCancel.then<Null>(_indexChanged, onError: _indexChanged);
.whenCompleteOrCancel(() {
_indexIsChangingCount -= 1;
notifyListeners();
});
} else {
_indexIsChangingCount += 1;
_animationController.value = _index.toDouble();
@ -111,12 +114,6 @@ class TabController extends ChangeNotifier {
}
}
Null _indexChanged(dynamic value) {
_indexIsChangingCount -= 1;
notifyListeners();
return null;
}
/// The index of the currently selected tab. Changing the index also updates
/// [previousIndex], sets the [animation]'s value to index, resets
/// [indexIsChanging] to false, and notifies listeners.

View file

@ -333,6 +333,9 @@ class Ticker {
/// [orCancel], which returns a derivative [Future] that completes with an error
/// if the [Ticker] that returned the [TickerFuture] was stopped with `canceled`
/// set to true, or if it was disposed without being stopped.
///
/// To run a callback when either this future resolves or when the tricker is
/// canceled, use [whenCompleteOrCancel].
class TickerFuture implements Future<Null> {
TickerFuture._();
@ -364,6 +367,16 @@ class TickerFuture implements Future<Null> {
_secondaryCompleter?.completeError(new TickerCanceled(ticker));
}
/// Calls `callback` either when this future resolves or when the ticker is
/// canceled.
void whenCompleteOrCancel(VoidCallback callback) {
Null thunk(dynamic value) {
callback();
return null;
}
orCancel.then(thunk, onError: thunk);
}
/// A future that resolves when this future resolves or throws when the ticker
/// is canceled.
Future<Null> get orCancel {

View file

@ -58,7 +58,7 @@ abstract class Route<T> {
///
/// The returned value resolves when the push transition is complete.
@protected
Future<Null> didPush() => new Future<Null>.value();
TickerFuture didPush() => new TickerFuture.complete();
/// When this route is popped (see [Navigator.pop]) if the result isn't
/// specified or if it's null, this value will be used instead.
@ -894,7 +894,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
newRoute._navigator = this;
newRoute.install(_currentOverlayEntry);
_history[index] = newRoute;
newRoute.didPush().then<Null>((Null value) {
newRoute.didPush().whenCompleteOrCancel(() {
// The old route's exit is not animated. We're assuming that the
// new route completely obscures the old one.
if (mounted) {

View file

@ -165,7 +165,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
}
@override
Future<Null> didPush() {
TickerFuture didPush() {
_animation.addStatusListener(_handleStatusChanged);
return _controller.forward();
}
@ -704,7 +704,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
}
@override
Future<Null> didPush() {
TickerFuture didPush() {
navigator.focusScopeNode.setFirstFocus(focusScopeNode);
return super.didPush();
}

View file

@ -0,0 +1,42 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
void main() {
testWidgets('Back during pushReplacement',
(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('a'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.pushReplacementNamed('/b');
await tester.pump();
await tester.pump(const Duration(milliseconds: 10));
expect(find.text('a'), findsOneWidget);
expect(find.text('b'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.pop();
await tester.pumpAndSettle();
expect(find.text('a'), findsNothing);
expect(find.text('b'), findsNothing);
expect(find.text('home'), findsOneWidget);
});
}

View file

@ -38,7 +38,7 @@ class TestRoute extends LocalHistoryRoute<String> {
}
@override
Future<Null> didPush() {
TickerFuture didPush() {
log('didPush');
return super.didPush();
}