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.didPopRoute] and
|
||||||
/// [WidgetsBindingObserver.didPushRoute], which expose this channel's
|
/// [WidgetsBindingObserver.didPushRoute], which expose this channel's
|
||||||
/// methods.
|
/// 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(
|
static const MethodChannel navigation = MethodChannel(
|
||||||
'flutter/navigation',
|
'flutter/navigation',
|
||||||
JSONMethodCodec(),
|
JSONMethodCodec(),
|
||||||
|
@ -9,6 +9,7 @@ import 'dart:developer' as developer;
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'binding.dart';
|
import 'binding.dart';
|
||||||
@ -16,6 +17,7 @@ import 'focus_manager.dart';
|
|||||||
import 'focus_scope.dart';
|
import 'focus_scope.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'overlay.dart';
|
import 'overlay.dart';
|
||||||
|
import 'route_notification_messages.dart';
|
||||||
import 'routes.dart';
|
import 'routes.dart';
|
||||||
import 'ticker_provider.dart';
|
import 'ticker_provider.dart';
|
||||||
|
|
||||||
@ -66,6 +68,18 @@ enum RoutePopDisposition {
|
|||||||
bubble,
|
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].
|
/// An abstraction for an entry managed by a [Navigator].
|
||||||
///
|
///
|
||||||
/// This class defines an abstract interface between the navigator and the
|
/// 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)
|
for (NavigatorObserver observer in widget.observers)
|
||||||
observer.didPush(route, oldRoute);
|
observer.didPush(route, oldRoute);
|
||||||
|
RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
|
||||||
assert(() { _debugLocked = false; return true; }());
|
assert(() { _debugLocked = false; return true; }());
|
||||||
_afterNavigation(route);
|
_afterNavigation(route);
|
||||||
return route.popped;
|
return route.popped;
|
||||||
@ -1854,6 +1869,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
for (NavigatorObserver observer in widget.observers)
|
for (NavigatorObserver observer in widget.observers)
|
||||||
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
|
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
|
||||||
|
RouteNotificationMessages.maybeNotifyRouteChange(_routeReplacedMethod, newRoute, oldRoute);
|
||||||
assert(() { _debugLocked = false; return true; }());
|
assert(() { _debugLocked = false; return true; }());
|
||||||
_afterNavigation(newRoute);
|
_afterNavigation(newRoute);
|
||||||
return newRoute.popped;
|
return newRoute.popped;
|
||||||
@ -1965,6 +1981,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
for (NavigatorObserver observer in widget.observers)
|
for (NavigatorObserver observer in widget.observers)
|
||||||
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
|
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
|
||||||
|
RouteNotificationMessages.maybeNotifyRouteChange(_routeReplacedMethod, newRoute, oldRoute);
|
||||||
oldRoute.dispose();
|
oldRoute.dispose();
|
||||||
assert(() { _debugLocked = false; return true; }());
|
assert(() { _debugLocked = false; return true; }());
|
||||||
}
|
}
|
||||||
@ -2067,6 +2084,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
|||||||
_history.last.didPopNext(route);
|
_history.last.didPopNext(route);
|
||||||
for (NavigatorObserver observer in widget.observers)
|
for (NavigatorObserver observer in widget.observers)
|
||||||
observer.didPop(route, _history.last);
|
observer.didPop(route, _history.last);
|
||||||
|
RouteNotificationMessages.maybeNotifyRouteChange(_routePoppedMethod, route, _history.last);
|
||||||
} else {
|
} else {
|
||||||
assert(() { _debugLocked = false; return true; }());
|
assert(() { _debugLocked = false; return true; }());
|
||||||
return false;
|
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 assetFolderPath = Platform.environment['UNIT_TEST_ASSETS'];
|
||||||
final String prefix = 'packages/${Platform.environment['APP_NAME']}/';
|
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) {
|
defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData message) {
|
||||||
String key = utf8.decode(message.buffer.asUint8List());
|
String key = utf8.decode(message.buffer.asUint8List());
|
||||||
File asset = File(path.join(assetFolderPath, key));
|
File asset = File(path.join(assetFolderPath, key));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user