Unverified Commit 058a9343 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Migrate the navigator API to routeInformationUpdated. (#82594)

parent b9645bfd
......@@ -32,10 +32,19 @@ class SystemChannels {
/// The following methods are used for the opposite direction data flow. The
/// framework notifies the engine about the route changes.
///
/// * `routeUpdated`, which is called when current route has changed.
/// * `selectSingleEntryHistory`, which enables a single-entry history mode.
///
/// * `routeInformationUpdated`, which is called by the [Router] when the
/// application navigate to a new location.
/// * `selectMultiEntryHistory`, which enables a multiple-entry history mode.
///
/// * `routeInformationUpdated`, which is called when the application
/// navigates to a new location, and which takes two arguments, `location`
/// (a URL) and `state` (an object).
///
/// * `routeUpdated`, a deprecated API which can be called in the same
/// situations as `routeInformationUpdated` but whose arguments are
/// `routeName` (a URL) and `previousRouteName` (which is ignored).
///
/// These APIs are exposed by the [SystemNavigator] class.
///
/// See also:
///
......
......@@ -33,15 +33,50 @@ class SystemNavigator {
await SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop', animated);
}
/// Selects the single-entry history mode.
///
/// On web, this switches the browser history model to one that only tracks a
/// single entry, so that calling [routeInformationUpdated] replaces the
/// current entry.
///
/// Currently, this is ignored on other platforms.
///
/// See also:
///
/// * [selectMultiEntryHistory], which enables the browser history to have
/// multiple entries.
static Future<void> selectSingleEntryHistory() {
return SystemChannels.navigation.invokeMethod<void>('selectSingleEntryHistory');
}
/// Selects the multiple-entry history mode.
///
/// On web, this switches the browser history model to one that tracks alll
/// updates to [routeInformationUpdated] to form a history stack. This is the
/// default.
///
/// Currently, this is ignored on other platforms.
///
/// See also:
///
/// * [selectSingleEntryHistory], which forces the history to only have one
/// entry.
static Future<void> selectMultiEntryHistory() {
return SystemChannels.navigation.invokeMethod<void>('selectMultiEntryHistory');
}
/// Notifies the platform for a route information change.
///
/// On Web, creates a new browser history entry and update URL with the route
/// information.
static void routeInformationUpdated({
/// On web, creates a new browser history entry and update URL with the route
/// information. Whether the history holds one entry or multiple entries is
/// determined by [selectSingleEntryHistory] and [selectMultiEntryHistory].
///
/// Currently, this is ignored on other platforms.
static Future<void> routeInformationUpdated({
required String location,
Object? state,
}) {
SystemChannels.navigation.invokeMethod<void>(
return SystemChannels.navigation.invokeMethod<void>(
'routeInformationUpdated',
<String, dynamic>{
'location': location,
......@@ -50,14 +85,22 @@ class SystemNavigator {
);
}
/// Notifies the platform of a route change.
/// Notifies the platform of a route change, and selects single-entry history
/// mode.
///
/// This is equivalent to calling [selectSingleEntryHistory] and
/// [routeInformationUpdated] together.
///
/// On Web, updates the URL bar with the [routeName].
static void routeUpdated({
/// The `previousRouteName` argument is ignored.
@Deprecated(
'Use routeInformationUpdated instead. '
'This feature was deprecated after v2.3.0-1.0.pre.'
)
static Future<void> routeUpdated({
String? routeName,
String? previousRouteName,
}) {
SystemChannels.navigation.invokeMethod<void>(
return SystemChannels.navigation.invokeMethod<void>(
'routeUpdated',
<String, dynamic>{
'previousRouteName': previousRouteName,
......
......@@ -1624,6 +1624,13 @@ class Navigator extends StatefulWidget {
/// route update message to the engine when it detects top-most route changes.
/// The messages are used by the web engine to update the browser URL bar.
///
/// If the property is set to true when the [Navigator] is first created,
/// single-entry history mode is requested using
/// [SystemNavigator.selectSingleEntryHistory]. This means this property
/// should not be used at the same time as [PlatformRouteInformationProvider]
/// is used with a [Router] (including when used with [MaterialApp.router],
/// for example).
///
/// If there are multiple navigators in the widget tree, at most one of them
/// can set this property to true (typically, the top-most one created from
/// the [WidgetsApp]). Otherwise, the web engine may receive multiple route
......@@ -3397,6 +3404,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
.getElementForInheritedWidgetOfExactType<HeroControllerScope>()
?.widget as HeroControllerScope?;
_updateHeroController(heroControllerScope?.controller);
if (widget.reportsRouteUpdateToEngine) {
SystemNavigator.selectSingleEntryHistory();
}
}
// Use [_nextPagelessRestorationScopeId] to get the next id.
......@@ -4062,17 +4073,14 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
// notifications.
_flushRouteAnnouncement();
// Announces route name changes.
// Announce route name changes.
if (widget.reportsRouteUpdateToEngine) {
final _RouteEntry? lastEntry = _history.cast<_RouteEntry?>().lastWhere(
(_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e), orElse: () => null,
);
final String? routeName = lastEntry?.route.settings.name;
if (routeName != _lastAnnouncedRouteName) {
SystemNavigator.routeUpdated(
routeName: routeName,
previousRouteName: _lastAnnouncedRouteName,
);
if (routeName != null && routeName != _lastAnnouncedRouteName) {
SystemNavigator.routeInformationUpdated(location: routeName);
_lastAnnouncedRouteName = routeName;
}
}
......
......@@ -1318,6 +1318,11 @@ abstract class RouteInformationProvider extends ValueListenable<RouteInformation
/// This provider also reports the new route information from the [Router] widget
/// back to engine using message channel method, the
/// [SystemNavigator.routeInformationUpdated].
///
/// Each time [SystemNavigator.routeInformationUpdated] is called, the
/// [SystemNavigator.selectMultiEntryHistory] method is also called. This
/// overrides the initialization behavior of
/// [Navigator.reportsRouteUpdateToEngine].
class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {
/// Create a platform route information provider.
///
......@@ -1329,6 +1334,7 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid
@override
void routerReportsNewRouteInformation(RouteInformation routeInformation) {
SystemNavigator.selectMultiEntryHistory();
SystemNavigator.routeInformationUpdated(
location: routeInformation.location!,
state: routeInformation.state,
......
......@@ -2,23 +2,58 @@
// 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/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('System navigator control test', () async {
final List<MethodCall> log = <MethodCall>[];
final List<MethodCall> log = <MethodCall>[];
Future<void> verify(AsyncCallback test, List<Object> expectations) async {
log.clear();
await test();
expect(log, expectations);
}
test('System navigator control test - platform messages', () async {
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await SystemNavigator.pop();
await verify(() => SystemNavigator.pop(), <Object>[
isMethodCall('SystemNavigator.pop', arguments: null),
]);
SystemChannels.platform.setMockMethodCallHandler(null);
});
test('System navigator control test - navigation messages', () async {
SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await verify(() => SystemNavigator.selectSingleEntryHistory(), <Object>[
isMethodCall('selectSingleEntryHistory', arguments: null),
]);
await verify(() => SystemNavigator.selectMultiEntryHistory(), <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
]);
await verify(() => SystemNavigator.routeInformationUpdated(location: 'a'), <Object>[
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'a', 'state': null }),
]);
await verify(() => SystemNavigator.routeInformationUpdated(location: 'a', state: true), <Object>[
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'a', 'state': true }),
]);
await verify(() => SystemNavigator.routeUpdated(routeName: 'a', previousRouteName: 'b'), <Object>[
isMethodCall('routeUpdated', arguments: <String, dynamic>{ 'routeName': 'a', 'previousRouteName': 'b' }),
]);
expect(log, hasLength(1));
expect(log.single, isMethodCall('SystemNavigator.pop', arguments: null));
SystemChannels.navigation.setMockMethodCallHandler(null);
});
}
......@@ -63,46 +63,46 @@ void main() {
routes: routes,
));
expect(log, hasLength(1));
expect(
log.last,
isMethodCall(
'routeUpdated',
expect(log, <Object>[
isMethodCall('selectSingleEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated',
arguments: <String, dynamic>{
'previousRouteName': null,
'routeName': '/',
'location': '/',
'state': null,
},
),
);
]);
log.clear();
await tester.tap(find.text('/'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(log, hasLength(2));
expect(log, hasLength(1));
expect(
log.last,
isMethodCall(
'routeUpdated',
'routeInformationUpdated',
arguments: <String, dynamic>{
'previousRouteName': '/',
'routeName': '/A',
'location': '/A',
'state': null,
},
),
);
log.clear();
await tester.tap(find.text('A'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(log, hasLength(3));
expect(log, hasLength(1));
expect(
log.last,
isMethodCall(
'routeUpdated',
'routeInformationUpdated',
arguments: <String, dynamic>{
'previousRouteName': '/A',
'routeName': '/',
'location': '/',
'state': null,
},
),
);
......@@ -168,46 +168,46 @@ void main() {
routes: routes,
));
expect(log, hasLength(1));
expect(
log.last,
isMethodCall(
'routeUpdated',
expect(log, <Object>[
isMethodCall('selectSingleEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated',
arguments: <String, dynamic>{
'previousRouteName': null,
'routeName': '/',
'location': '/',
'state': null,
},
),
);
]);
log.clear();
await tester.tap(find.text('/'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(log, hasLength(2));
expect(log, hasLength(1));
expect(
log.last,
isMethodCall(
'routeUpdated',
'routeInformationUpdated',
arguments: <String, dynamic>{
'previousRouteName': '/',
'routeName': '/A',
'location': '/A',
'state': null,
},
),
);
log.clear();
await tester.tap(find.text('A'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(log, hasLength(3));
expect(log, hasLength(1));
expect(
log.last,
isMethodCall(
'routeUpdated',
'routeInformationUpdated',
arguments: <String, dynamic>{
'previousRouteName': '/A',
'routeName': '/B',
'location': '/B',
'state': null,
},
),
);
......@@ -237,27 +237,22 @@ void main() {
},
));
expect(log, hasLength(1));
expect(
log.last,
isMethodCall('routeUpdated', arguments: <String, dynamic>{
'previousRouteName': null,
'routeName': '/home',
}),
);
expect(log, <Object>[
isMethodCall('selectSingleEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated',
arguments: <String, dynamic>{
'location': '/home',
'state': null,
},
),
]);
log.clear();
await tester.tap(find.text('Home'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(log, hasLength(2));
expect(
log.last,
isMethodCall('routeUpdated', arguments: <String, dynamic>{
'previousRouteName': '/home',
'routeName': null,
}),
);
expect(log, isEmpty);
});
testWidgets('PlatformRouteInformationProvider reports URL', (WidgetTester tester) async {
......@@ -294,16 +289,13 @@ void main() {
await tester.pump();
expect(find.text('update'), findsOneWidget);
expect(log, hasLength(1));
// TODO(chunhtai): check routeInformationUpdated instead once the engine
// side is done.
expect(
log.last,
expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
'location': 'update',
'state': 'state',
}),
);
]);
});
}
......
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