Unverified Commit c2e2f093 authored by Nurhan Turgut's avatar Nurhan Turgut Committed by GitHub

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
parent b7bab3c2
......@@ -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;
......
// 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,
},
);
}
}
}
// 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));
......
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