// 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_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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. Router<dynamic>? router = Router.maybeOf(textContext); expect(router, isNull); bool hasFlutterError = false; try { router = Router.of(textContext); } on FlutterError catch(e) { expect(e.message.startsWith('Router'), isTrue); hasFlutterError = true; } expect(hasFlutterError, isTrue); }); 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 passes only routeInformationProvider', (WidgetTester tester) async { final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(); provider.value = const RouteInformation( location: 'initial', ); try { Router<RouteInformation>( routeInformationProvider: provider, routerDelegate: SimpleRouterDelegate( builder: (BuildContext context, RouteInformation? information) { return Text(information!.location!); }, ), ); } on AssertionError catch(e) { expect( e.message, 'Both routeInformationProvider and routeInformationParser must be provided if this router ' 'parses route information. Otherwise, they should both be null.' ); } }); testWidgets('Router throw when passes only routeInformationParser', (WidgetTester tester) async { try { Router<RouteInformation>( routeInformationParser: SimpleRouteInformationParser(), routerDelegate: SimpleRouterDelegate( builder: (BuildContext context, RouteInformation? information) { return Text(information!.location!); }, ), ); } on AssertionError catch(e) { expect( e.message, 'Both routeInformationProvider and routeInformationParser must be provided if this router ' 'parses route information. Otherwise, they should both be null.' ); } }); 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 outter', ); 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 outter', ); 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 outter'), 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 takePrioirty 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; final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( onRouterReport: (RouteInformation information) { // Makes sure we only report once after manually cleaning up. expect(reportedRouteInformation, isNull); reportedRouteInformation = information; } ); 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, isNull); delegate.routeInformation = const RouteInformation( location: 'update', ); await tester.pump(); expect(find.text('initial'), findsNothing); expect(find.text('update'), findsOneWidget); expect(reportedRouteInformation!.location, 'update'); // The router should not report if only state changes. reportedRouteInformation = null; delegate.routeInformation = const RouteInformation( location: 'update', state: 'another state', ); await tester.pump(); expect(find.text('update'), findsOneWidget); expect(reportedRouteInformation, isNull); reportedRouteInformation = 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'); }); testWidgets('router can be forced to recognize or ignore navigating events', (WidgetTester tester) async { RouteInformation? reportedRouteInformation; bool isNavigating = false; late RouteInformation nextRouteInformation; final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( onRouterReport: (RouteInformation information) { // Makes sure we only report once after manually cleaning up. expect(reportedRouteInformation, isNull); reportedRouteInformation = information; } ); 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, isNull); 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(reportedRouteInformation, isNull); 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(reportedRouteInformation!.location, 'update'); }); testWidgets('router does not report when route information is up to date with route information provider', (WidgetTester tester) async { RouteInformation? reportedRouteInformation; final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( onRouterReport: (RouteInformation information) { reportedRouteInformation = information; } ); provider.value = const RouteInformation( location: 'initial', ); final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true); delegate.builder = (BuildContext context, RouteInformation? routeInformation) { return Text(routeInformation!.location!); }; await tester.pumpWidget(buildBoilerPlate( Router<RouteInformation>( routeInformationProvider: provider, routeInformationParser: SimpleRouteInformationParser(), routerDelegate: delegate, ) )); expect(find.text('initial'), findsOneWidget); expect(reportedRouteInformation, isNull); // This will cause the router to rebuild. provider.value = const RouteInformation( location: 'update', ); // This will schedule the route reporting. delegate.notifyListeners(); await tester.pump(); expect(find.text('initial'), findsNothing); expect(find.text('update'), findsOneWidget); // The router should not report because the route name is already up to // date. expect(reportedRouteInformation, isNull); }); 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('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); }); } 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); 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) { onRouterReport?.call(routeInformation); } } 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); }