diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 2732955beb8d..2eb1a7707c9b 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 10.1.0 + +- Supports setting `requestFocus`. + ## 10.0.0 - **BREAKING CHANGE**: diff --git a/packages/go_router/lib/src/builder.dart b/packages/go_router/lib/src/builder.dart index 0cfdf5efa481..15c58a96da7a 100644 --- a/packages/go_router/lib/src/builder.dart +++ b/packages/go_router/lib/src/builder.dart @@ -45,6 +45,7 @@ class RouteBuilder { required this.restorationScopeId, required this.observers, required this.onPopPageWithRouteMatch, + this.requestFocus = true, }); /// Builder function for a go router with Navigator. @@ -63,6 +64,12 @@ class RouteBuilder { /// its history. final String? restorationScopeId; + /// Whether or not the navigator created by this builder and it's new topmost route should request focus + /// when the new route is pushed onto the navigator. + /// + /// Defaults to true. + final bool requestFocus; + /// NavigatorObserver used to receive notifications when navigating in between routes. /// changes. final List observers; @@ -137,6 +144,7 @@ class RouteBuilder { navigatorKey, observers: observers, restorationScopeId: restorationScopeId, + requestFocus: requestFocus, ), ); } @@ -258,7 +266,10 @@ class RouteBuilder { // Build the Navigator for this shell route Widget buildShellNavigator( - List? observers, String? restorationScopeId) { + List? observers, + String? restorationScopeId, { + bool requestFocus = true, + }) { return _buildNavigator( pagePopContext.onPopPage, keyToPages[shellNavigatorKey]!, @@ -266,6 +277,7 @@ class RouteBuilder { observers: observers ?? const [], restorationScopeId: restorationScopeId, heroController: heroController, + requestFocus: requestFocus, ); } @@ -298,6 +310,7 @@ class RouteBuilder { List observers = const [], String? restorationScopeId, HeroController? heroController, + bool requestFocus = true, }) { final Widget navigator = Navigator( key: navigatorKey, @@ -305,6 +318,7 @@ class RouteBuilder { pages: pages, observers: observers, onPopPage: onPopPage, + requestFocus: requestFocus, ); if (heroController != null) { return HeroControllerScope( diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index 6265bb67f9e3..3a341be79124 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -26,6 +26,7 @@ class GoRouterDelegate extends RouterDelegate required List observers, required this.routerNeglect, String? restorationScopeId, + bool requestFocus = true, }) : _configuration = configuration { builder = RouteBuilder( configuration: configuration, @@ -35,6 +36,7 @@ class GoRouterDelegate extends RouterDelegate restorationScopeId: restorationScopeId, observers: observers, onPopPageWithRouteMatch: _handlePopPageWithRouteMatch, + requestFocus: requestFocus, ); } diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index 7de73207a224..a5671e2fc9d0 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -49,6 +49,8 @@ typedef GoExceptionHandler = void Function( /// See [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html) /// for more details. /// +/// To disable automatically requesting focus when new routes are pushed to the navigator, set `requestFocus` to false. +/// /// See also: /// * [Configuration](https://pub.dev/documentation/go_router/latest/topics/Configuration-topic.html) /// * [GoRoute], which provides APIs to define the routing table. @@ -83,6 +85,7 @@ class GoRouter implements RouterConfig { bool debugLogDiagnostics = false, GlobalKey? navigatorKey, String? restorationScopeId, + bool requestFocus = true, }) : backButtonDispatcher = RootBackButtonDispatcher(), assert( initialExtra == null || initialLocation != null, @@ -147,6 +150,7 @@ class GoRouter implements RouterConfig { ...observers ?? [], ], restorationScopeId: restorationScopeId, + requestFocus: requestFocus, // wrap the returned Navigator to enable GoRouter.of(context).go() et al, // allowing the caller to wrap the navigator themselves builderWithNav: (BuildContext context, Widget child) => diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index dafbfc4e6047..3ad93e7ee933 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,7 +1,7 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 10.0.0 +version: 10.1.0 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22 diff --git a/packages/go_router/test/request_focus.dart b/packages/go_router/test/request_focus.dart new file mode 100644 index 000000000000..f9ccc9681f30 --- /dev/null +++ b/packages/go_router/test/request_focus.dart @@ -0,0 +1,61 @@ +// Copyright 2013 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/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; + +void main() { + testWidgets('GoRouter does not request focus if requestFocus is false', + (WidgetTester tester) async { + final GlobalKey innerKey = GlobalKey(); + final FocusScopeNode focusNode = FocusScopeNode(); + final GoRouter router = GoRouter( + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + name: 'home', + builder: (_, __) => const Text('A'), + ), + GoRoute( + path: '/second', + name: 'second', + builder: (_, __) => Text('B', key: innerKey), + ), + ], + requestFocus: false, + ); + + await tester.pumpWidget(Column( + children: [ + FocusScope(node: focusNode, child: Container()), + Expanded( + child: MaterialApp.router( + routerConfig: router, + ), + ), + ], + )); + + expect(find.text('A'), findsOneWidget); + expect(find.text('B', skipOffstage: false), findsNothing); + expect(focusNode.hasFocus, false); + focusNode.requestFocus(); + await tester.pumpAndSettle(); + expect(focusNode.hasFocus, true); + + router.pushNamed('second'); + await tester.pumpAndSettle(); + expect(find.text('A', skipOffstage: false), findsOneWidget); + expect(find.text('B'), findsOneWidget); + expect(focusNode.hasFocus, true); + + router.pop(); + await tester.pumpAndSettle(); + expect(find.text('A'), findsOneWidget); + expect(find.text('B', skipOffstage: false), findsNothing); + expect(focusNode.hasFocus, true); + }); +} diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart index 71c89109f996..38420cd29138 100644 --- a/packages/go_router/test/test_helpers.dart +++ b/packages/go_router/test/test_helpers.dart @@ -149,6 +149,7 @@ Future createRouter( GoRouterWidgetBuilder? errorBuilder, String? restorationScopeId, GoExceptionHandler? onException, + bool requestFocus = true, }) async { final GoRouter goRouter = GoRouter( routes: routes, @@ -160,6 +161,7 @@ Future createRouter( errorBuilder: errorBuilder, navigatorKey: navigatorKey, restorationScopeId: restorationScopeId, + requestFocus: requestFocus, ); await tester.pumpWidget( MaterialApp.router(