Navigator change backup (#38494)
* Broadcasting popRoute and pushRoute methods via SystemChannels.navigation. These messages will be used in flutter_web to detect the route * Broadcasting popRoute and pushRoute methods via SystemChannels.navigation. These messages will be used in flutter_web to detect the route * Reverting all unrelated formatting changes. * Adding unit tests. Adding more comments. * Changing string method names with constant strings. * Fixing a constant strings. * Fixing analyzer error. * Fixing more white space. * Changing the method names. Adding comments to the SystemChannels * Comment and code name fixes * replacing the comment with reviewer suggestion. * addinf systemchannels.navigation mock to test bindings * Adding a new class for sending route change notrifications. The nottifications are only sent on web. This should fix breaking android/ios * using new class RouteNotificationMessages in navigator * Fixing analyzer issues. * fixing cycle dependency * fixing github analyze error * dartfmt two new classes. trying to fix anayze errors * Update route_notification_messages.dart * trying to fix white space errors
This commit is contained in:
parent
b7bab3c2f0
commit
c2e2f093ec
@ -27,6 +27,24 @@ class SystemChannels {
|
||||
/// * [WidgetsBindingObserver.didPopRoute] and
|
||||
/// [WidgetsBindingObserver.didPushRoute], which expose this channel's
|
||||
/// methods.
|
||||
///
|
||||
/// The following methods are used for the opposite direction data flow. The
|
||||
/// framework notifies the engine about the route changes.
|
||||
///
|
||||
/// * `routePushed`, which is called when a route is pushed. (e.g. A modal
|
||||
/// replaces the entire screen.)
|
||||
///
|
||||
/// * `routePopped`, which is called when a route is popped. (e.g. A dialog,
|
||||
/// such as time picker is closed.)
|
||||
///
|
||||
/// * `routeReplaced`, which is called when a route is replaced.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Navigator] which manages transitions from one page to another.
|
||||
/// [Navigator.push], [Navigator.pushReplacement], [Navigator.pop] and
|
||||
/// [Navigator.replace], utilize this channel's methods to send route
|
||||
/// change information from framework to engine.
|
||||
static const MethodChannel navigation = MethodChannel(
|
||||
'flutter/navigation',
|
||||
JSONMethodCodec(),
|
||||
|
@ -9,6 +9,7 @@ import 'dart:developer' as developer;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'basic.dart';
|
||||
import 'binding.dart';
|
||||
@ -16,6 +17,7 @@ import 'focus_manager.dart';
|
||||
import 'focus_scope.dart';
|
||||
import 'framework.dart';
|
||||
import 'overlay.dart';
|
||||
import 'route_notification_messages.dart';
|
||||
import 'routes.dart';
|
||||
import 'ticker_provider.dart';
|
||||
|
||||
@ -66,6 +68,18 @@ enum RoutePopDisposition {
|
||||
bubble,
|
||||
}
|
||||
|
||||
/// Name for the method which is used for sending messages from framework to
|
||||
/// engine after a route is popped.
|
||||
const String _routePoppedMethod = 'routePopped';
|
||||
|
||||
/// Name for the method which is used for sending messages from framework to
|
||||
/// engine after a route is pushed.
|
||||
const String _routePushedMethod = 'routePushed';
|
||||
|
||||
/// Name for the method which is used for sending messages from framework to
|
||||
/// engine after a route is replaced.
|
||||
const String _routeReplacedMethod = 'routeReplaced';
|
||||
|
||||
/// An abstraction for an entry managed by a [Navigator].
|
||||
///
|
||||
/// This class defines an abstract interface between the navigator and the
|
||||
@ -1761,6 +1775,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
||||
}
|
||||
for (NavigatorObserver observer in widget.observers)
|
||||
observer.didPush(route, oldRoute);
|
||||
RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
|
||||
assert(() { _debugLocked = false; return true; }());
|
||||
_afterNavigation(route);
|
||||
return route.popped;
|
||||
@ -1854,6 +1869,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
||||
}
|
||||
for (NavigatorObserver observer in widget.observers)
|
||||
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
|
||||
RouteNotificationMessages.maybeNotifyRouteChange(_routeReplacedMethod, newRoute, oldRoute);
|
||||
assert(() { _debugLocked = false; return true; }());
|
||||
_afterNavigation(newRoute);
|
||||
return newRoute.popped;
|
||||
@ -1965,6 +1981,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
||||
}
|
||||
for (NavigatorObserver observer in widget.observers)
|
||||
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
|
||||
RouteNotificationMessages.maybeNotifyRouteChange(_routeReplacedMethod, newRoute, oldRoute);
|
||||
oldRoute.dispose();
|
||||
assert(() { _debugLocked = false; return true; }());
|
||||
}
|
||||
@ -2067,6 +2084,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
||||
_history.last.didPopNext(route);
|
||||
for (NavigatorObserver observer in widget.observers)
|
||||
observer.didPop(route, _history.last);
|
||||
RouteNotificationMessages.maybeNotifyRouteChange(_routePoppedMethod, route, _history.last);
|
||||
} else {
|
||||
assert(() { _debugLocked = false; return true; }());
|
||||
return false;
|
||||
|
@ -0,0 +1,42 @@
|
||||
// Copyright 2015 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/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'navigator.dart';
|
||||
|
||||
/// Messages for route change notifications.
|
||||
class RouteNotificationMessages {
|
||||
RouteNotificationMessages._();
|
||||
|
||||
/// When the engine is Web notify the platform for a route change.
|
||||
static void maybeNotifyRouteChange(String methodName, Route<dynamic> route, Route<dynamic> previousRoute) {
|
||||
if(kIsWeb) {
|
||||
_notifyRouteChange(methodName, route, previousRoute);
|
||||
} else {
|
||||
// No op.
|
||||
}
|
||||
}
|
||||
|
||||
/// Notifies the platform of a route change.
|
||||
///
|
||||
/// There are three methods: 'routePushed', 'routePopped', 'routeReplaced'.
|
||||
///
|
||||
/// See also [SystemChannels.navigation], which handles subsequent navigation
|
||||
/// requests.
|
||||
static void _notifyRouteChange(String methodName, Route<dynamic> route, Route<dynamic> previousRoute) {
|
||||
final String previousRouteName = previousRoute?.settings?.name;
|
||||
final String routeName = route?.settings?.name;
|
||||
if (previousRouteName != null || routeName != null) {
|
||||
SystemChannels.navigation.invokeMethod<void>(
|
||||
methodName,
|
||||
<String, dynamic>{
|
||||
'previousRouteName': previousRouteName,
|
||||
'routeName': routeName,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
@TestOn('chrome')
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class OnTapPage extends StatelessWidget {
|
||||
const OnTapPage({Key key, this.id, this.onTap}) : super(key: key);
|
||||
|
||||
final String id;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Page $id')),
|
||||
body: GestureDetector(
|
||||
onTap: onTap,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
child: Center(
|
||||
child: Text(id, style: Theme.of(context).textTheme.display2),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('Push and Pop should send platform messages',
|
||||
(WidgetTester tester) async {
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (BuildContext context) => OnTapPage(
|
||||
id: '/',
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, '/A');
|
||||
}),
|
||||
'/A': (BuildContext context) => OnTapPage(
|
||||
id: 'A',
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
};
|
||||
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
|
||||
SystemChannels.navigation
|
||||
.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||
log.add(methodCall);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
routes: routes,
|
||||
));
|
||||
|
||||
expect(log, hasLength(1));
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall(
|
||||
'routePushed',
|
||||
arguments: <String, dynamic>{
|
||||
'previousRouteName': null,
|
||||
'routeName': '/'
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('/'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(log, hasLength(2));
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall(
|
||||
'routePushed',
|
||||
arguments: <String, dynamic>{
|
||||
'previousRouteName': '/',
|
||||
'routeName': '/A'
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('A'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(log, hasLength(3));
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall(
|
||||
'routePopped',
|
||||
arguments: <String, dynamic>{
|
||||
'previousRouteName': '/',
|
||||
'routeName': '/A'
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('Replace should send platform messages',
|
||||
(WidgetTester tester) async {
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (BuildContext context) => OnTapPage(
|
||||
id: '/',
|
||||
onTap: () {
|
||||
Navigator.pushNamed(context, '/A');
|
||||
}),
|
||||
'/A': (BuildContext context) => OnTapPage(
|
||||
id: 'A',
|
||||
onTap: () {
|
||||
Navigator.pushReplacementNamed(context, '/B');
|
||||
}),
|
||||
'/B': (BuildContext context) => OnTapPage(id: 'B', onTap: () {}),
|
||||
};
|
||||
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
|
||||
SystemChannels.navigation
|
||||
.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||
log.add(methodCall);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
routes: routes,
|
||||
));
|
||||
|
||||
expect(log, hasLength(1));
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall(
|
||||
'routePushed',
|
||||
arguments: <String, dynamic>{
|
||||
'previousRouteName': null,
|
||||
'routeName': '/'
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('/'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(log, hasLength(2));
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall(
|
||||
'routePushed',
|
||||
arguments: <String, dynamic>{
|
||||
'previousRouteName': '/',
|
||||
'routeName': '/A'
|
||||
},
|
||||
));
|
||||
|
||||
await tester.tap(find.text('A'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(log, hasLength(3));
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall(
|
||||
'routeReplaced',
|
||||
arguments: <String, dynamic>{
|
||||
'previousRouteName': '/A',
|
||||
'routeName': '/B'
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
@ -808,6 +808,10 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
||||
final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS'];
|
||||
final String prefix = 'packages/${Platform.environment['APP_NAME']}/';
|
||||
|
||||
/// Navigation related actions (pop, push, replace) broadcasts these actions via
|
||||
/// platform messages.
|
||||
SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {});
|
||||
|
||||
defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData message) {
|
||||
String key = utf8.decode(message.buffer.asUint8List());
|
||||
File asset = File(path.join(assetFolderPath, key));
|
||||
|
Loading…
x
Reference in New Issue
Block a user