go_router should allow setting requestFocus (#4636)

fixes: https://github.com/flutter/flutter/issues/129581
This commit is contained in:
hangyu
2023-08-09 13:36:47 -07:00
committed by GitHub
parent 175ff5603b
commit 4ff585ef1c
7 changed files with 89 additions and 2 deletions

View File

@ -1,3 +1,7 @@
## 10.1.0
- Supports setting `requestFocus`.
## 10.0.0 ## 10.0.0
- **BREAKING CHANGE**: - **BREAKING CHANGE**:

View File

@ -45,6 +45,7 @@ class RouteBuilder {
required this.restorationScopeId, required this.restorationScopeId,
required this.observers, required this.observers,
required this.onPopPageWithRouteMatch, required this.onPopPageWithRouteMatch,
this.requestFocus = true,
}); });
/// Builder function for a go router with Navigator. /// Builder function for a go router with Navigator.
@ -63,6 +64,12 @@ class RouteBuilder {
/// its history. /// its history.
final String? restorationScopeId; 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. /// NavigatorObserver used to receive notifications when navigating in between routes.
/// changes. /// changes.
final List<NavigatorObserver> observers; final List<NavigatorObserver> observers;
@ -137,6 +144,7 @@ class RouteBuilder {
navigatorKey, navigatorKey,
observers: observers, observers: observers,
restorationScopeId: restorationScopeId, restorationScopeId: restorationScopeId,
requestFocus: requestFocus,
), ),
); );
} }
@ -258,7 +266,10 @@ class RouteBuilder {
// Build the Navigator for this shell route // Build the Navigator for this shell route
Widget buildShellNavigator( Widget buildShellNavigator(
List<NavigatorObserver>? observers, String? restorationScopeId) { List<NavigatorObserver>? observers,
String? restorationScopeId, {
bool requestFocus = true,
}) {
return _buildNavigator( return _buildNavigator(
pagePopContext.onPopPage, pagePopContext.onPopPage,
keyToPages[shellNavigatorKey]!, keyToPages[shellNavigatorKey]!,
@ -266,6 +277,7 @@ class RouteBuilder {
observers: observers ?? const <NavigatorObserver>[], observers: observers ?? const <NavigatorObserver>[],
restorationScopeId: restorationScopeId, restorationScopeId: restorationScopeId,
heroController: heroController, heroController: heroController,
requestFocus: requestFocus,
); );
} }
@ -298,6 +310,7 @@ class RouteBuilder {
List<NavigatorObserver> observers = const <NavigatorObserver>[], List<NavigatorObserver> observers = const <NavigatorObserver>[],
String? restorationScopeId, String? restorationScopeId,
HeroController? heroController, HeroController? heroController,
bool requestFocus = true,
}) { }) {
final Widget navigator = Navigator( final Widget navigator = Navigator(
key: navigatorKey, key: navigatorKey,
@ -305,6 +318,7 @@ class RouteBuilder {
pages: pages, pages: pages,
observers: observers, observers: observers,
onPopPage: onPopPage, onPopPage: onPopPage,
requestFocus: requestFocus,
); );
if (heroController != null) { if (heroController != null) {
return HeroControllerScope( return HeroControllerScope(

View File

@ -26,6 +26,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
required List<NavigatorObserver> observers, required List<NavigatorObserver> observers,
required this.routerNeglect, required this.routerNeglect,
String? restorationScopeId, String? restorationScopeId,
bool requestFocus = true,
}) : _configuration = configuration { }) : _configuration = configuration {
builder = RouteBuilder( builder = RouteBuilder(
configuration: configuration, configuration: configuration,
@ -35,6 +36,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
restorationScopeId: restorationScopeId, restorationScopeId: restorationScopeId,
observers: observers, observers: observers,
onPopPageWithRouteMatch: _handlePopPageWithRouteMatch, onPopPageWithRouteMatch: _handlePopPageWithRouteMatch,
requestFocus: requestFocus,
); );
} }

View File

@ -49,6 +49,8 @@ typedef GoExceptionHandler = void Function(
/// See [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html) /// See [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html)
/// for more details. /// for more details.
/// ///
/// To disable automatically requesting focus when new routes are pushed to the navigator, set `requestFocus` to false.
///
/// See also: /// See also:
/// * [Configuration](https://pub.dev/documentation/go_router/latest/topics/Configuration-topic.html) /// * [Configuration](https://pub.dev/documentation/go_router/latest/topics/Configuration-topic.html)
/// * [GoRoute], which provides APIs to define the routing table. /// * [GoRoute], which provides APIs to define the routing table.
@ -83,6 +85,7 @@ class GoRouter implements RouterConfig<RouteMatchList> {
bool debugLogDiagnostics = false, bool debugLogDiagnostics = false,
GlobalKey<NavigatorState>? navigatorKey, GlobalKey<NavigatorState>? navigatorKey,
String? restorationScopeId, String? restorationScopeId,
bool requestFocus = true,
}) : backButtonDispatcher = RootBackButtonDispatcher(), }) : backButtonDispatcher = RootBackButtonDispatcher(),
assert( assert(
initialExtra == null || initialLocation != null, initialExtra == null || initialLocation != null,
@ -147,6 +150,7 @@ class GoRouter implements RouterConfig<RouteMatchList> {
...observers ?? <NavigatorObserver>[], ...observers ?? <NavigatorObserver>[],
], ],
restorationScopeId: restorationScopeId, restorationScopeId: restorationScopeId,
requestFocus: requestFocus,
// wrap the returned Navigator to enable GoRouter.of(context).go() et al, // wrap the returned Navigator to enable GoRouter.of(context).go() et al,
// allowing the caller to wrap the navigator themselves // allowing the caller to wrap the navigator themselves
builderWithNav: (BuildContext context, Widget child) => builderWithNav: (BuildContext context, Widget child) =>

View File

@ -1,7 +1,7 @@
name: go_router name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more 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 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 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22

View File

@ -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>[
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: <Widget>[
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);
});
}

View File

@ -149,6 +149,7 @@ Future<GoRouter> createRouter(
GoRouterWidgetBuilder? errorBuilder, GoRouterWidgetBuilder? errorBuilder,
String? restorationScopeId, String? restorationScopeId,
GoExceptionHandler? onException, GoExceptionHandler? onException,
bool requestFocus = true,
}) async { }) async {
final GoRouter goRouter = GoRouter( final GoRouter goRouter = GoRouter(
routes: routes, routes: routes,
@ -160,6 +161,7 @@ Future<GoRouter> createRouter(
errorBuilder: errorBuilder, errorBuilder: errorBuilder,
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
restorationScopeId: restorationScopeId, restorationScopeId: restorationScopeId,
requestFocus: requestFocus,
); );
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp.router( MaterialApp.router(