Migrate the navigator API to routeInformationUpdated. (#82594)
This commit is contained in:
parent
b9645bfd30
commit
058a9343dc
@ -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.
|
||||
///
|
||||
/// On Web, updates the URL bar with the [routeName].
|
||||
static void routeUpdated({
|
||||
/// This is equivalent to calling [selectSingleEntryHistory] and
|
||||
/// [routeInformationUpdated] together.
|
||||
///
|
||||
/// 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),
|
||||
]);
|
||||
|
||||
expect(log, hasLength(1));
|
||||
expect(log.single, 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' }),
|
||||
]);
|
||||
|
||||
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',
|
||||
}),
|
||||
);
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user