| // Copyright 2014 The Flutter 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/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| testWidgets('Simple router basic functionality - synchronized', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| return Text(information!.location!); |
| }, |
| ), |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| provider.value = const RouteInformation( |
| location: 'update', |
| ); |
| await tester.pump(); |
| expect(find.text('initial'), findsNothing); |
| expect(find.text('update'), findsOneWidget); |
| }); |
| |
| testWidgets('Simple router basic functionality - asynchronized', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final SimpleAsyncRouteInformationParser parser = SimpleAsyncRouteInformationParser(); |
| final SimpleAsyncRouterDelegate delegate = SimpleAsyncRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| if (information == null) |
| return const Text('waiting'); |
| return Text(information.location!); |
| }, |
| ); |
| await tester.runAsync(() async { |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| routeInformationProvider: provider, |
| routeInformationParser: parser, |
| routerDelegate: delegate, |
| ), |
| )); |
| // Future has not yet completed. |
| expect(find.text('waiting'), findsOneWidget); |
| |
| await parser.parsingFuture; |
| await delegate.setNewRouteFuture; |
| await tester.pump(); |
| expect(find.text('initial'), findsOneWidget); |
| |
| provider.value = const RouteInformation( |
| location: 'update', |
| ); |
| await tester.pump(); |
| // Future has not yet completed. |
| expect(find.text('initial'), findsOneWidget); |
| |
| await parser.parsingFuture; |
| await delegate.setNewRouteFuture; |
| await tester.pump(); |
| expect(find.text('update'), findsOneWidget); |
| }); |
| }); |
| |
| testWidgets('Router.maybeOf can be null', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(buildBoilerPlate( |
| Text('dummy', key: key), |
| )); |
| final BuildContext textContext = key.currentContext!; |
| |
| // This should not throw error. |
| final Router<dynamic>? router = Router.maybeOf(textContext); |
| expect(router, isNull); |
| |
| expect( |
| () => Router.of(textContext), |
| throwsA(isFlutterError.having((FlutterError e) => e.message, 'message', startsWith('Router'))) |
| ); |
| }); |
| |
| testWidgets('Simple router can handle pop route', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher dispatcher = RootBackButtonDispatcher(); |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| return Text(information!.location!); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| backButtonDispatcher: dispatcher, |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| bool result = false; |
| // SynchronousFuture should complete immediately. |
| dispatcher.invokeCallback(SynchronousFuture<bool>(false)) |
| .then((bool data) { |
| result = data; |
| }); |
| expect(result, isTrue); |
| |
| await tester.pump(); |
| expect(find.text('popped'), findsOneWidget); |
| }); |
| |
| testWidgets('Router throw when passing routeInformationProvider without routeInformationParser', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| expect( |
| () { |
| Router<RouteInformation>( |
| routeInformationProvider: provider, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| return Text(information!.location!); |
| }, |
| ), |
| ); |
| }, |
| throwsA(isAssertionError.having( |
| (AssertionError e) => e.message, |
| 'message', |
| 'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.', |
| )), |
| ); |
| }); |
| |
| testWidgets('Router throw when passing restorationId without routeInformationParser', (WidgetTester tester) async { |
| expect( |
| () { |
| Router<RouteInformation>( |
| restorationScopeId: 'foo', |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| return Text(information!.location!); |
| }, |
| ), |
| ); |
| }, |
| throwsA(isAssertionError.having( |
| (AssertionError e) => e.message, |
| 'message', |
| 'A routeInformationParser must be provided when a routeInformationProvider or a restorationId is specified.', |
| )), |
| ); |
| }); |
| |
| testWidgets('PopNavigatorRouterDelegateMixin works', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher dispatcher = RootBackButtonDispatcher(); |
| final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| return Text(information!.location!); |
| }, |
| onPopPage: (Route<void> route, void result) { |
| provider.value = const RouteInformation( |
| location: 'popped', |
| ); |
| return route.didPop(result); |
| }, |
| ); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: delegate, |
| backButtonDispatcher: dispatcher, |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| // Pushes a nameless route. |
| showDialog<void>( |
| useRootNavigator: false, |
| context: delegate.navigatorKey.currentContext!, |
| builder: (BuildContext context) => const Text('dialog'), |
| ); |
| await tester.pumpAndSettle(); |
| expect(find.text('dialog'), findsOneWidget); |
| |
| // Pops the nameless route and makes sure the initial page is shown. |
| bool result = false; |
| result = await dispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| |
| await tester.pumpAndSettle(); |
| expect(find.text('initial'), findsOneWidget); |
| expect(find.text('dialog'), findsNothing); |
| |
| // Pops one more time. |
| result = false; |
| result = await dispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pumpAndSettle(); |
| expect(find.text('popped'), findsOneWidget); |
| }); |
| |
| testWidgets('Nested routers back button dispatcher works', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| final BackButtonDispatcher innerDispatcher = ChildBackButtonDispatcher(outerDispatcher); |
| innerDispatcher.takePriority(); |
| // Creates the sub-router. |
| return Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Text(information!.location!); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| // The outer dispatcher should trigger the pop on the inner router. |
| bool result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped inner'), findsOneWidget); |
| }); |
| |
| testWidgets('Nested router back button dispatcher works for multiple children', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| final BackButtonDispatcher innerDispatcher1 = ChildBackButtonDispatcher(outerDispatcher); |
| final BackButtonDispatcher innerDispatcher2 = ChildBackButtonDispatcher(outerDispatcher); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher1, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Container(); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner1', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ), |
| Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher2, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Container(); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner2', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ), |
| ], |
| ); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| // If none of the children have taken the priority, the root router handles |
| // the pop. |
| bool result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped outer'), findsOneWidget); |
| |
| innerDispatcher1.takePriority(); |
| result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped inner1'), findsOneWidget); |
| |
| // The last child dispatcher that took priority handles the pop. |
| innerDispatcher2.takePriority(); |
| result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped inner2'), findsOneWidget); |
| }); |
| |
| testWidgets('ChildBackButtonDispatcher can be replaced without calling the takePriority', (WidgetTester tester) async { |
| |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| BackButtonDispatcher innerDispatcher = ChildBackButtonDispatcher(outerDispatcher); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| const Text('initial'), |
| Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Container(); |
| }, |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ), |
| )); |
| |
| // Creates a new child back button dispatcher and rebuild, this will cause |
| // the old one to be replaced and discarded. |
| innerDispatcher = ChildBackButtonDispatcher(outerDispatcher); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| const Text('initial'), |
| Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Container(); |
| }, |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ), |
| )); |
| |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester tester) async { |
| |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| final BackButtonDispatcher innerDispatcher1 = ChildBackButtonDispatcher(outerDispatcher); |
| final BackButtonDispatcher innerDispatcher2 = ChildBackButtonDispatcher(innerDispatcher1); |
| final BackButtonDispatcher innerDispatcher3 = ChildBackButtonDispatcher(innerDispatcher2); |
| bool isPopped = false; |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher1, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher2, |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Router<RouteInformation>( |
| backButtonDispatcher: innerDispatcher3, |
| routerDelegate: SimpleRouterDelegate( |
| onPopRoute: () { |
| isPopped = true; |
| return SynchronousFuture<bool>(true); |
| }, |
| builder: (BuildContext context, RouteInformation? innerInformation) { |
| return Container(); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ), |
| )); |
| // This should work without calling the takePriority on the innerDispatcher2 |
| // and the innerDispatcher1. |
| innerDispatcher3.takePriority(); |
| bool result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| expect(isPopped, isTrue); |
| }); |
| |
| testWidgets('router does report URL change correctly', (WidgetTester tester) async { |
| RouteInformation? reportedRouteInformation; |
| RouteInformationReportingType? reportedType; |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( |
| onRouterReport: (RouteInformation information, RouteInformationReportingType type) { |
| // Makes sure we only report once after manually cleaning up. |
| expect(reportedRouteInformation, isNull); |
| expect(reportedType, isNull); |
| reportedRouteInformation = information; |
| reportedType = type; |
| }, |
| ); |
| final SimpleRouterDelegate delegate = SimpleRouterDelegate( |
| reportConfiguration: true, |
| builder: (BuildContext context, RouteInformation? information) { |
| return Text(information!.location!); |
| }, |
| ); |
| delegate.onPopRoute = () { |
| delegate.routeInformation = const RouteInformation( |
| location: 'popped', |
| ); |
| return SynchronousFuture<bool>(true); |
| }; |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: delegate, |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| expect(reportedRouteInformation!.location, 'initial'); |
| expect(reportedType, RouteInformationReportingType.none); |
| reportedRouteInformation = null; |
| reportedType = null; |
| delegate.routeInformation = const RouteInformation( |
| location: 'update', |
| ); |
| await tester.pump(); |
| expect(find.text('initial'), findsNothing); |
| expect(find.text('update'), findsOneWidget); |
| expect(reportedRouteInformation!.location, 'update'); |
| expect(reportedType, RouteInformationReportingType.none); |
| |
| // The router should report as non navigation event if only state changes. |
| reportedRouteInformation = null; |
| reportedType = null; |
| delegate.routeInformation = const RouteInformation( |
| location: 'update', |
| state: 'another state', |
| ); |
| await tester.pump(); |
| expect(find.text('update'), findsOneWidget); |
| expect(reportedRouteInformation!.location, 'update'); |
| expect(reportedRouteInformation!.state, 'another state'); |
| expect(reportedType, RouteInformationReportingType.none); |
| |
| reportedRouteInformation = null; |
| reportedType = null; |
| bool result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped'), findsOneWidget); |
| expect(reportedRouteInformation!.location, 'popped'); |
| expect(reportedType, RouteInformationReportingType.none); |
| }); |
| |
| testWidgets('router can be forced to recognize or ignore navigating events', (WidgetTester tester) async { |
| RouteInformation? reportedRouteInformation; |
| RouteInformationReportingType? reportedType; |
| bool isNavigating = false; |
| late RouteInformation nextRouteInformation; |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( |
| onRouterReport: (RouteInformation information, RouteInformationReportingType type) { |
| // Makes sure we only report once after manually cleaning up. |
| expect(reportedRouteInformation, isNull); |
| expect(reportedType, isNull); |
| reportedRouteInformation = information; |
| reportedType = type; |
| }, |
| ); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true); |
| delegate.builder = (BuildContext context, RouteInformation? information) { |
| return ElevatedButton( |
| child: Text(information!.location!), |
| onPressed: () { |
| if (isNavigating) { |
| Router.navigate(context, () { |
| if (delegate.routeInformation != nextRouteInformation) |
| delegate.routeInformation = nextRouteInformation; |
| }); |
| } else { |
| Router.neglect(context, () { |
| if (delegate.routeInformation != nextRouteInformation) |
| delegate.routeInformation = nextRouteInformation; |
| }); |
| } |
| }, |
| ); |
| }; |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: delegate, |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| expect(reportedRouteInformation!.location, 'initial'); |
| expect(reportedType, RouteInformationReportingType.none); |
| reportedType = null; |
| reportedRouteInformation = null; |
| |
| nextRouteInformation = const RouteInformation( |
| location: 'update', |
| ); |
| await tester.tap(find.byType(ElevatedButton)); |
| await tester.pump(); |
| expect(find.text('initial'), findsNothing); |
| expect(find.text('update'), findsOneWidget); |
| expect(reportedType, RouteInformationReportingType.neglect); |
| expect(reportedRouteInformation!.location, 'update'); |
| reportedType = null; |
| reportedRouteInformation = null; |
| |
| isNavigating = true; |
| // This should not trigger any real navigating event because the |
| // nextRouteInformation does not change. However, the router should still |
| // report a route information because isNavigating = true. |
| await tester.tap(find.byType(ElevatedButton)); |
| await tester.pump(); |
| expect(reportedType, RouteInformationReportingType.navigate); |
| expect(reportedRouteInformation!.location, 'update'); |
| reportedType = null; |
| reportedRouteInformation = null; |
| }); |
| |
| testWidgets('router ignore navigating events updates RouteInformationProvider', (WidgetTester tester) async { |
| RouteInformation? updatedRouteInformation; |
| late RouteInformation nextRouteInformation; |
| RouteInformationReportingType? reportingType; |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( |
| onRouterReport: (RouteInformation information, RouteInformationReportingType type) { |
| expect(reportingType, isNull); |
| expect(updatedRouteInformation, isNull); |
| updatedRouteInformation = information; |
| reportingType = type; |
| }, |
| ); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true); |
| delegate.builder = (BuildContext context, RouteInformation? information) { |
| return ElevatedButton( |
| child: Text(information!.location!), |
| onPressed: () { |
| Router.neglect(context, () { |
| if (delegate.routeInformation != nextRouteInformation) |
| delegate.routeInformation = nextRouteInformation; |
| }); |
| }, |
| ); |
| }; |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: delegate, |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| expect(updatedRouteInformation!.location, 'initial'); |
| expect(reportingType, RouteInformationReportingType.none); |
| updatedRouteInformation = null; |
| reportingType = null; |
| |
| nextRouteInformation = const RouteInformation( |
| location: 'update', |
| ); |
| await tester.tap(find.byType(ElevatedButton)); |
| await tester.pump(); |
| expect(find.text('initial'), findsNothing); |
| expect(find.text('update'), findsOneWidget); |
| expect(updatedRouteInformation!.location, 'update'); |
| expect(reportingType, RouteInformationReportingType.neglect); |
| }); |
| |
| testWidgets('state change without location changes updates RouteInformationProvider', (WidgetTester tester) async { |
| RouteInformation? updatedRouteInformation; |
| late RouteInformation nextRouteInformation; |
| RouteInformationReportingType? reportingType; |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( |
| onRouterReport: (RouteInformation information, RouteInformationReportingType type) { |
| // This should never be a navigation event. |
| expect(reportingType, isNull); |
| expect(updatedRouteInformation, isNull); |
| updatedRouteInformation = information; |
| reportingType = type; |
| }, |
| ); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| state: 'state1', |
| ); |
| final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true); |
| delegate.builder = (BuildContext context, RouteInformation? information) { |
| return ElevatedButton( |
| child: Text(information!.location!), |
| onPressed: () { |
| delegate.routeInformation = nextRouteInformation; |
| }, |
| ); |
| }; |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: delegate, |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| expect(updatedRouteInformation!.location, 'initial'); |
| expect(reportingType, RouteInformationReportingType.none); |
| updatedRouteInformation = null; |
| reportingType = null; |
| |
| nextRouteInformation = const RouteInformation( |
| location: 'initial', |
| state: 'state2', |
| ); |
| await tester.tap(find.byType(ElevatedButton)); |
| await tester.pump(); |
| expect(updatedRouteInformation!.location, 'initial'); |
| expect(updatedRouteInformation!.state, 'state2'); |
| expect(reportingType, RouteInformationReportingType.none); |
| }); |
| |
| testWidgets('PlatformRouteInformationProvider works', (WidgetTester tester) async { |
| final RouteInformationProvider provider = PlatformRouteInformationProvider( |
| initialRouteInformation: const RouteInformation( |
| location: 'initial', |
| ), |
| ); |
| final SimpleRouterDelegate delegate = SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| final List<Widget> children = <Widget>[]; |
| if (information!.location! != null) |
| children.add(Text(information.location!)); |
| if (information.state != null) |
| children.add(Text(information.state.toString())); |
| return Column( |
| children: children, |
| ); |
| }, |
| ); |
| |
| await tester.pumpWidget(MaterialApp.router( |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: delegate, |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| // Pushes through the `pushRouteInformation` in the navigation method channel. |
| const Map<String, dynamic> testRouteInformation = <String, dynamic>{ |
| 'location': 'testRouteName', |
| 'state': 'state', |
| }; |
| final ByteData routerMessage = const JSONMethodCodec().encodeMethodCall( |
| const MethodCall('pushRouteInformation', testRouteInformation), |
| ); |
| await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', routerMessage, (_) { }); |
| await tester.pump(); |
| expect(find.text('testRouteName'), findsOneWidget); |
| expect(find.text('state'), findsOneWidget); |
| |
| // Pushes through the `pushRoute` in the navigation method channel. |
| const String testRouteName = 'newTestRouteName'; |
| final ByteData message = const JSONMethodCodec().encodeMethodCall( |
| const MethodCall('pushRoute', testRouteName), |
| ); |
| await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); |
| await tester.pump(); |
| expect(find.text('newTestRouteName'), findsOneWidget); |
| }); |
| |
| testWidgets('PlatformRouteInformationProvider updates route information', (WidgetTester tester) async { |
| final List<MethodCall> log = <MethodCall>[]; |
| TestDefaultBinaryMessengerBinding |
| .instance! |
| .defaultBinaryMessenger |
| .setMockMethodCallHandler( |
| SystemChannels.navigation, |
| (MethodCall methodCall) async { |
| log.add(methodCall); |
| } |
| ); |
| final RouteInformationProvider provider = PlatformRouteInformationProvider( |
| initialRouteInformation: const RouteInformation( |
| location: 'initial', |
| ), |
| ); |
| |
| log.clear(); |
| provider.routerReportsNewRouteInformation(const RouteInformation(location: 'a', state: true), type: RouteInformationReportingType.none); |
| // Implicit reporting pushes new history entry if the location changes. |
| expect(log, <Object>[ |
| isMethodCall('selectMultiEntryHistory', arguments: null), |
| isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'a', 'state': true, 'replace': false }), |
| ]); |
| log.clear(); |
| provider.routerReportsNewRouteInformation(const RouteInformation(location: 'a', state: false), type: RouteInformationReportingType.none); |
| // Since the location is the same, the provider sends replaces message. |
| expect(log, <Object>[ |
| isMethodCall('selectMultiEntryHistory', arguments: null), |
| isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'a', 'state': false, 'replace': true }), |
| ]); |
| |
| log.clear(); |
| provider.routerReportsNewRouteInformation(const RouteInformation(location: 'b', state: false), type: RouteInformationReportingType.neglect); |
| expect(log, <Object>[ |
| isMethodCall('selectMultiEntryHistory', arguments: null), |
| isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'b', 'state': false, 'replace': true }), |
| ]); |
| |
| log.clear(); |
| provider.routerReportsNewRouteInformation(const RouteInformation(location: 'b', state: false), type: RouteInformationReportingType.navigate); |
| expect(log, <Object>[ |
| isMethodCall('selectMultiEntryHistory', arguments: null), |
| isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'b', 'state': false, 'replace': false }), |
| ]); |
| }); |
| |
| testWidgets('RootBackButtonDispatcher works', (WidgetTester tester) async { |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| final RouteInformationProvider provider = PlatformRouteInformationProvider( |
| initialRouteInformation: const RouteInformation( |
| location: 'initial', |
| ), |
| ); |
| final SimpleRouterDelegate delegate = SimpleRouterDelegate( |
| reportConfiguration: true, |
| builder: (BuildContext context, RouteInformation? information) { |
| return Text(information!.location!); |
| }, |
| ); |
| delegate.onPopRoute = () { |
| delegate.routeInformation = const RouteInformation( |
| location: 'popped', |
| ); |
| return SynchronousFuture<bool>(true); |
| }; |
| |
| await tester.pumpWidget(MaterialApp.router( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: delegate, |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| // Pop route through the message channel. |
| final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute')); |
| await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); |
| await tester.pump(); |
| expect(find.text('popped'), findsOneWidget); |
| }); |
| |
| testWidgets('BackButtonListener takes priority over root back dispatcher', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| BackButtonListener( |
| child: Container(), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner1', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ], |
| ); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| bool result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped inner1'), findsOneWidget); |
| }); |
| |
| testWidgets('BackButtonListener updates callback if it has been changed', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| final SimpleRouterDelegate routerDelegate = SimpleRouterDelegate() |
| ..builder = (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| BackButtonListener( |
| child: Container(), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'first callback', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ], |
| ); |
| } |
| ..onPopRoute = () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }; |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: routerDelegate, |
| ), |
| )); |
| |
| routerDelegate |
| ..builder = (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| BackButtonListener( |
| child: Container(), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'second callback', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ], |
| ); |
| } |
| ..onPopRoute = () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }; |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: routerDelegate, |
| ), |
| )); |
| await tester.pump(); |
| await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| await tester.pump(); |
| expect(find.text('second callback'), findsOneWidget); |
| }); |
| |
| testWidgets('BackButtonListener clears callback if it is disposed', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| final SimpleRouterDelegate routerDelegate = SimpleRouterDelegate() |
| ..builder = (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| BackButtonListener( |
| child: Container(), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'first callback', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ], |
| ); |
| } |
| ..onPopRoute = () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }; |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: routerDelegate, |
| ), |
| )); |
| |
| routerDelegate |
| ..builder = (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| ], |
| ); |
| } |
| ..onPopRoute = () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }; |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: routerDelegate, |
| ), |
| )); |
| await tester.pump(); |
| await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| await tester.pump(); |
| expect(find.text('popped outer'), findsOneWidget); |
| }); |
| |
| testWidgets('Nested backButtonListener should take priority', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| BackButtonListener( |
| child: BackButtonListener( |
| child: Container(), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner2', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner1', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ], |
| ); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| bool result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped inner2'), findsOneWidget); |
| }); |
| |
| testWidgets('Nested backButtonListener that returns false should call next on the line', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| BackButtonListener( |
| child: BackButtonListener( |
| child: Container(), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner2', |
| ); |
| return SynchronousFuture<bool>(false); |
| }, |
| ), |
| onBackButtonPressed: () { |
| provider.value = const RouteInformation( |
| location: 'popped inner1', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ], |
| ); |
| }, |
| onPopRoute: () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ), |
| ), |
| )); |
| expect(find.text('initial'), findsOneWidget); |
| |
| bool result = false; |
| result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| expect(result, isTrue); |
| await tester.pump(); |
| expect(find.text('popped inner1'), findsOneWidget); |
| }); |
| |
| testWidgets('`didUpdateWidget` test', (WidgetTester tester) async { |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); |
| provider.value = const RouteInformation( |
| location: 'initial', |
| ); |
| final BackButtonDispatcher outerDispatcher = RootBackButtonDispatcher(); |
| late StateSetter setState; |
| String location = 'first callback'; |
| final SimpleRouterDelegate routerDelegate = SimpleRouterDelegate() |
| ..builder = (BuildContext context, RouteInformation? information) { |
| // Creates the sub-router. |
| return Column( |
| children: <Widget>[ |
| Text(information!.location!), |
| StatefulBuilder( |
| builder: (BuildContext context, StateSetter setter) { |
| setState = setter; |
| return BackButtonListener( |
| child: Container(), |
| onBackButtonPressed: () { |
| provider.value = RouteInformation( |
| location: location, |
| ); |
| return SynchronousFuture<bool>(true); |
| }, |
| ); |
| }, |
| ), |
| ], |
| ); |
| } |
| ..onPopRoute = () { |
| provider.value = const RouteInformation( |
| location: 'popped outer', |
| ); |
| return SynchronousFuture<bool>(true); |
| }; |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| backButtonDispatcher: outerDispatcher, |
| routeInformationProvider: provider, |
| routeInformationParser: SimpleRouteInformationParser(), |
| routerDelegate: routerDelegate, |
| ), |
| )); |
| |
| // Only update BackButtonListener widget. |
| setState(() { |
| location = 'second callback'; |
| }); |
| |
| await tester.pump(); |
| await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); |
| await tester.pump(); |
| expect(find.text('second callback'), findsOneWidget); |
| }); |
| |
| testWidgets('Router reports location if it is different from location given by OS', (WidgetTester tester) async { |
| final List<RouteInformation> reportedRouteInformation = <RouteInformation>[]; |
| final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( |
| onRouterReport: (RouteInformation info, RouteInformationReportingType type) => reportedRouteInformation.add(info), |
| )..value = const RouteInformation(location: '/home'); |
| |
| await tester.pumpWidget(buildBoilerPlate( |
| Router<RouteInformation>( |
| routeInformationProvider: provider, |
| routeInformationParser: RedirectingInformationParser(<String, RouteInformation>{ |
| '/doesNotExist' : const RouteInformation(location: '/404'), |
| }), |
| routerDelegate: SimpleRouterDelegate( |
| builder: (BuildContext _, RouteInformation? info) => Text('Current route: ${info?.location}'), |
| reportConfiguration: true, |
| ), |
| ), |
| )); |
| |
| expect(find.text('Current route: /home'), findsOneWidget); |
| expect(reportedRouteInformation.single.location, '/home'); |
| |
| provider.value = const RouteInformation(location: '/doesNotExist'); |
| await tester.pump(); |
| |
| expect(find.text('Current route: /404'), findsOneWidget); |
| expect(reportedRouteInformation[1].location, '/404'); |
| }); |
| } |
| |
| Widget buildBoilerPlate(Widget child) { |
| return MaterialApp( |
| home: Scaffold( |
| body: child, |
| ), |
| ); |
| } |
| |
| typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation?); |
| typedef SimpleRouterDelegatePopRoute = Future<bool> Function(); |
| typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result); |
| typedef RouterReportRouterInformation = void Function(RouteInformation, RouteInformationReportingType); |
| |
| class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> { |
| SimpleRouteInformationParser(); |
| |
| @override |
| Future<RouteInformation> parseRouteInformation(RouteInformation information) { |
| return SynchronousFuture<RouteInformation>(information); |
| } |
| |
| @override |
| RouteInformation restoreRouteInformation(RouteInformation configuration) { |
| return configuration; |
| } |
| } |
| |
| class SimpleRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier { |
| SimpleRouterDelegate({ |
| this.builder, |
| this.onPopRoute, |
| this.reportConfiguration = false, |
| }); |
| |
| RouteInformation? get routeInformation => _routeInformation; |
| RouteInformation? _routeInformation; |
| set routeInformation(RouteInformation? newValue) { |
| _routeInformation = newValue; |
| notifyListeners(); |
| } |
| |
| SimpleRouterDelegateBuilder? builder; |
| SimpleRouterDelegatePopRoute? onPopRoute; |
| final bool reportConfiguration; |
| |
| @override |
| RouteInformation? get currentConfiguration { |
| if (reportConfiguration) |
| return routeInformation; |
| return null; |
| } |
| |
| @override |
| Future<void> setNewRoutePath(RouteInformation configuration) { |
| _routeInformation = configuration; |
| return SynchronousFuture<void>(null); |
| } |
| |
| @override |
| Future<bool> popRoute() { |
| return onPopRoute?.call() ?? SynchronousFuture<bool>(true); |
| } |
| |
| @override |
| Widget build(BuildContext context) => builder!(context, routeInformation); |
| } |
| |
| class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> with PopNavigatorRouterDelegateMixin<RouteInformation>, ChangeNotifier { |
| SimpleNavigatorRouterDelegate({ |
| required this.builder, |
| required this.onPopPage, |
| }); |
| |
| @override |
| GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| |
| RouteInformation get routeInformation => _routeInformation; |
| late RouteInformation _routeInformation; |
| set routeInformation(RouteInformation newValue) { |
| _routeInformation = newValue; |
| notifyListeners(); |
| } |
| |
| SimpleRouterDelegateBuilder builder; |
| SimpleNavigatorRouterDelegatePopPage<void> onPopPage; |
| |
| @override |
| Future<void> setNewRoutePath(RouteInformation configuration) { |
| _routeInformation = configuration; |
| return SynchronousFuture<void>(null); |
| } |
| |
| bool _handlePopPage(Route<void> route, void data) { |
| return onPopPage(route, data); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Navigator( |
| key: navigatorKey, |
| onPopPage: _handlePopPage, |
| pages: <Page<void>>[ |
| // We need at least two pages for the pop to propagate through. |
| // Otherwise, the navigator will bubble the pop to the system navigator. |
| const MaterialPage<void>( |
| child: Text('base'), |
| ), |
| MaterialPage<void>( |
| key: ValueKey<String>(routeInformation.location!), |
| child: builder(context, routeInformation), |
| ), |
| ], |
| ); |
| } |
| } |
| |
| class SimpleRouteInformationProvider extends RouteInformationProvider with ChangeNotifier { |
| SimpleRouteInformationProvider({ |
| this.onRouterReport, |
| }); |
| |
| RouterReportRouterInformation? onRouterReport; |
| |
| @override |
| RouteInformation get value => _value; |
| late RouteInformation _value; |
| set value(RouteInformation newValue) { |
| _value = newValue; |
| notifyListeners(); |
| } |
| |
| @override |
| void routerReportsNewRouteInformation(RouteInformation routeInformation, {required RouteInformationReportingType type}) { |
| _value = routeInformation; |
| onRouterReport?.call(routeInformation, type); |
| } |
| } |
| |
| class SimpleAsyncRouteInformationParser extends RouteInformationParser<RouteInformation> { |
| SimpleAsyncRouteInformationParser(); |
| |
| late Future<RouteInformation> parsingFuture; |
| |
| @override |
| Future<RouteInformation> parseRouteInformation(RouteInformation information) { |
| return parsingFuture = Future<RouteInformation>.value(information); |
| } |
| |
| @override |
| RouteInformation restoreRouteInformation(RouteInformation configuration) { |
| return configuration; |
| } |
| } |
| |
| class SimpleAsyncRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier { |
| SimpleAsyncRouterDelegate({ |
| required this.builder, |
| }); |
| |
| RouteInformation? get routeInformation => _routeInformation; |
| RouteInformation? _routeInformation; |
| set routeInformation(RouteInformation? newValue) { |
| _routeInformation = newValue; |
| notifyListeners(); |
| } |
| |
| SimpleRouterDelegateBuilder builder; |
| late Future<void> setNewRouteFuture; |
| |
| @override |
| Future<void> setNewRoutePath(RouteInformation configuration) { |
| _routeInformation = configuration; |
| return setNewRouteFuture = Future<void>.value(); |
| } |
| |
| @override |
| Future<bool> popRoute() { |
| return Future<bool>.value(true); |
| } |
| |
| @override |
| Widget build(BuildContext context) => builder(context, routeInformation); |
| } |
| |
| class RedirectingInformationParser extends RouteInformationParser<RouteInformation> { |
| |
| RedirectingInformationParser(this.redirects); |
| |
| final Map<String, RouteInformation> redirects; |
| |
| @override |
| Future<RouteInformation> parseRouteInformation(RouteInformation information) { |
| return SynchronousFuture<RouteInformation>(redirects[information.location] ?? information); |
| } |
| |
| @override |
| RouteInformation restoreRouteInformation(RouteInformation configuration) { |
| return configuration; |
| } |
| } |