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
|
/// The following methods are used for the opposite direction data flow. The
|
||||||
/// framework notifies the engine about the route changes.
|
/// 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
|
/// * `selectMultiEntryHistory`, which enables a multiple-entry history mode.
|
||||||
/// application navigate to a new location.
|
///
|
||||||
|
/// * `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:
|
/// See also:
|
||||||
///
|
///
|
||||||
|
@ -33,15 +33,50 @@ class SystemNavigator {
|
|||||||
await SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop', animated);
|
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.
|
/// Notifies the platform for a route information change.
|
||||||
///
|
///
|
||||||
/// On Web, creates a new browser history entry and update URL with the route
|
/// On web, creates a new browser history entry and update URL with the route
|
||||||
/// information.
|
/// information. Whether the history holds one entry or multiple entries is
|
||||||
static void routeInformationUpdated({
|
/// determined by [selectSingleEntryHistory] and [selectMultiEntryHistory].
|
||||||
|
///
|
||||||
|
/// Currently, this is ignored on other platforms.
|
||||||
|
static Future<void> routeInformationUpdated({
|
||||||
required String location,
|
required String location,
|
||||||
Object? state,
|
Object? state,
|
||||||
}) {
|
}) {
|
||||||
SystemChannels.navigation.invokeMethod<void>(
|
return SystemChannels.navigation.invokeMethod<void>(
|
||||||
'routeInformationUpdated',
|
'routeInformationUpdated',
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'location': location,
|
'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].
|
/// This is equivalent to calling [selectSingleEntryHistory] and
|
||||||
static void routeUpdated({
|
/// [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? routeName,
|
||||||
String? previousRouteName,
|
String? previousRouteName,
|
||||||
}) {
|
}) {
|
||||||
SystemChannels.navigation.invokeMethod<void>(
|
return SystemChannels.navigation.invokeMethod<void>(
|
||||||
'routeUpdated',
|
'routeUpdated',
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'previousRouteName': previousRouteName,
|
'previousRouteName': previousRouteName,
|
||||||
|
@ -1624,6 +1624,13 @@ class Navigator extends StatefulWidget {
|
|||||||
/// route update message to the engine when it detects top-most route changes.
|
/// 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.
|
/// 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
|
/// 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
|
/// can set this property to true (typically, the top-most one created from
|
||||||
/// the [WidgetsApp]). Otherwise, the web engine may receive multiple route
|
/// the [WidgetsApp]). Otherwise, the web engine may receive multiple route
|
||||||
@ -3397,6 +3404,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
|||||||
.getElementForInheritedWidgetOfExactType<HeroControllerScope>()
|
.getElementForInheritedWidgetOfExactType<HeroControllerScope>()
|
||||||
?.widget as HeroControllerScope?;
|
?.widget as HeroControllerScope?;
|
||||||
_updateHeroController(heroControllerScope?.controller);
|
_updateHeroController(heroControllerScope?.controller);
|
||||||
|
|
||||||
|
if (widget.reportsRouteUpdateToEngine) {
|
||||||
|
SystemNavigator.selectSingleEntryHistory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use [_nextPagelessRestorationScopeId] to get the next id.
|
// Use [_nextPagelessRestorationScopeId] to get the next id.
|
||||||
@ -4062,17 +4073,14 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
|||||||
// notifications.
|
// notifications.
|
||||||
_flushRouteAnnouncement();
|
_flushRouteAnnouncement();
|
||||||
|
|
||||||
// Announces route name changes.
|
// Announce route name changes.
|
||||||
if (widget.reportsRouteUpdateToEngine) {
|
if (widget.reportsRouteUpdateToEngine) {
|
||||||
final _RouteEntry? lastEntry = _history.cast<_RouteEntry?>().lastWhere(
|
final _RouteEntry? lastEntry = _history.cast<_RouteEntry?>().lastWhere(
|
||||||
(_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e), orElse: () => null,
|
(_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e), orElse: () => null,
|
||||||
);
|
);
|
||||||
final String? routeName = lastEntry?.route.settings.name;
|
final String? routeName = lastEntry?.route.settings.name;
|
||||||
if (routeName != _lastAnnouncedRouteName) {
|
if (routeName != null && routeName != _lastAnnouncedRouteName) {
|
||||||
SystemNavigator.routeUpdated(
|
SystemNavigator.routeInformationUpdated(location: routeName);
|
||||||
routeName: routeName,
|
|
||||||
previousRouteName: _lastAnnouncedRouteName,
|
|
||||||
);
|
|
||||||
_lastAnnouncedRouteName = 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
|
/// This provider also reports the new route information from the [Router] widget
|
||||||
/// back to engine using message channel method, the
|
/// back to engine using message channel method, the
|
||||||
/// [SystemNavigator.routeInformationUpdated].
|
/// [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 {
|
class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {
|
||||||
/// Create a platform route information provider.
|
/// Create a platform route information provider.
|
||||||
///
|
///
|
||||||
@ -1329,6 +1334,7 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void routerReportsNewRouteInformation(RouteInformation routeInformation) {
|
void routerReportsNewRouteInformation(RouteInformation routeInformation) {
|
||||||
|
SystemNavigator.selectMultiEntryHistory();
|
||||||
SystemNavigator.routeInformationUpdated(
|
SystemNavigator.routeInformationUpdated(
|
||||||
location: routeInformation.location!,
|
location: routeInformation.location!,
|
||||||
state: routeInformation.state,
|
state: routeInformation.state,
|
||||||
|
@ -2,23 +2,58 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
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 {
|
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||||
log.add(methodCall);
|
log.add(methodCall);
|
||||||
});
|
});
|
||||||
|
|
||||||
await SystemNavigator.pop();
|
await verify(() => SystemNavigator.pop(), <Object>[
|
||||||
|
isMethodCall('SystemNavigator.pop', arguments: null),
|
||||||
|
]);
|
||||||
|
|
||||||
expect(log, hasLength(1));
|
SystemChannels.platform.setMockMethodCallHandler(null);
|
||||||
expect(log.single, isMethodCall('SystemNavigator.pop', arguments: 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,
|
routes: routes,
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(log, hasLength(1));
|
expect(log, <Object>[
|
||||||
expect(
|
isMethodCall('selectSingleEntryHistory', arguments: null),
|
||||||
log.last,
|
isMethodCall('routeInformationUpdated',
|
||||||
isMethodCall(
|
|
||||||
'routeUpdated',
|
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'previousRouteName': null,
|
'location': '/',
|
||||||
'routeName': '/',
|
'state': null,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
]);
|
||||||
|
log.clear();
|
||||||
|
|
||||||
await tester.tap(find.text('/'));
|
await tester.tap(find.text('/'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
expect(log, hasLength(2));
|
expect(log, hasLength(1));
|
||||||
expect(
|
expect(
|
||||||
log.last,
|
log.last,
|
||||||
isMethodCall(
|
isMethodCall(
|
||||||
'routeUpdated',
|
'routeInformationUpdated',
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'previousRouteName': '/',
|
'location': '/A',
|
||||||
'routeName': '/A',
|
'state': null,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
log.clear();
|
||||||
|
|
||||||
await tester.tap(find.text('A'));
|
await tester.tap(find.text('A'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
expect(log, hasLength(3));
|
expect(log, hasLength(1));
|
||||||
expect(
|
expect(
|
||||||
log.last,
|
log.last,
|
||||||
isMethodCall(
|
isMethodCall(
|
||||||
'routeUpdated',
|
'routeInformationUpdated',
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'previousRouteName': '/A',
|
'location': '/',
|
||||||
'routeName': '/',
|
'state': null,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -168,46 +168,46 @@ void main() {
|
|||||||
routes: routes,
|
routes: routes,
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(log, hasLength(1));
|
expect(log, <Object>[
|
||||||
expect(
|
isMethodCall('selectSingleEntryHistory', arguments: null),
|
||||||
log.last,
|
isMethodCall('routeInformationUpdated',
|
||||||
isMethodCall(
|
|
||||||
'routeUpdated',
|
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'previousRouteName': null,
|
'location': '/',
|
||||||
'routeName': '/',
|
'state': null,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
]);
|
||||||
|
log.clear();
|
||||||
|
|
||||||
await tester.tap(find.text('/'));
|
await tester.tap(find.text('/'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
expect(log, hasLength(2));
|
expect(log, hasLength(1));
|
||||||
expect(
|
expect(
|
||||||
log.last,
|
log.last,
|
||||||
isMethodCall(
|
isMethodCall(
|
||||||
'routeUpdated',
|
'routeInformationUpdated',
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'previousRouteName': '/',
|
'location': '/A',
|
||||||
'routeName': '/A',
|
'state': null,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
log.clear();
|
||||||
|
|
||||||
await tester.tap(find.text('A'));
|
await tester.tap(find.text('A'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
expect(log, hasLength(3));
|
expect(log, hasLength(1));
|
||||||
expect(
|
expect(
|
||||||
log.last,
|
log.last,
|
||||||
isMethodCall(
|
isMethodCall(
|
||||||
'routeUpdated',
|
'routeInformationUpdated',
|
||||||
arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'previousRouteName': '/A',
|
'location': '/B',
|
||||||
'routeName': '/B',
|
'state': null,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -237,27 +237,22 @@ void main() {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(log, hasLength(1));
|
expect(log, <Object>[
|
||||||
expect(
|
isMethodCall('selectSingleEntryHistory', arguments: null),
|
||||||
log.last,
|
isMethodCall('routeInformationUpdated',
|
||||||
isMethodCall('routeUpdated', arguments: <String, dynamic>{
|
arguments: <String, dynamic>{
|
||||||
'previousRouteName': null,
|
'location': '/home',
|
||||||
'routeName': '/home',
|
'state': null,
|
||||||
}),
|
},
|
||||||
);
|
),
|
||||||
|
]);
|
||||||
|
log.clear();
|
||||||
|
|
||||||
await tester.tap(find.text('Home'));
|
await tester.tap(find.text('Home'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
expect(log, hasLength(2));
|
expect(log, isEmpty);
|
||||||
expect(
|
|
||||||
log.last,
|
|
||||||
isMethodCall('routeUpdated', arguments: <String, dynamic>{
|
|
||||||
'previousRouteName': '/home',
|
|
||||||
'routeName': null,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('PlatformRouteInformationProvider reports URL', (WidgetTester tester) async {
|
testWidgets('PlatformRouteInformationProvider reports URL', (WidgetTester tester) async {
|
||||||
@ -294,16 +289,13 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.text('update'), findsOneWidget);
|
expect(find.text('update'), findsOneWidget);
|
||||||
|
|
||||||
expect(log, hasLength(1));
|
expect(log, <Object>[
|
||||||
// TODO(chunhtai): check routeInformationUpdated instead once the engine
|
isMethodCall('selectMultiEntryHistory', arguments: null),
|
||||||
// side is done.
|
|
||||||
expect(
|
|
||||||
log.last,
|
|
||||||
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
|
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
|
||||||
'location': 'update',
|
'location': 'update',
|
||||||
'state': 'state',
|
'state': 'state',
|
||||||
}),
|
}),
|
||||||
);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user