Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
76b3c675
Unverified
Commit
76b3c675
authored
Sep 20, 2021
by
Casey Rogers
Committed by
GitHub
Sep 20, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow Developers to Stop Navigator From Requesting Focus (#90097)
parent
c9751c92
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
186 additions
and
29 deletions
+186
-29
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+35
-24
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+9
-5
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+142
-0
No files found.
packages/flutter/lib/src/widgets/navigator.dart
View file @
76b3c675
...
...
@@ -214,7 +214,9 @@ abstract class Route<T> {
@mustCallSuper
TickerFuture
didPush
()
{
return
TickerFuture
.
complete
()..
then
<
void
>((
void
_
)
{
navigator
?.
focusScopeNode
.
requestFocus
();
if
(
navigator
?.
widget
.
requestFocus
==
true
)
{
navigator
!.
focusScopeNode
.
requestFocus
();
}
});
}
...
...
@@ -228,30 +230,32 @@ abstract class Route<T> {
@protected
@mustCallSuper
void
didAdd
()
{
// This TickerFuture serves two purposes. First, we want to make sure
// animations triggered by other operations finish before focusing the
// navigator. Second, navigator.focusScopeNode might acquire more focused
// children in Route.install asynchronously. This TickerFuture will wait for
// it to finish first.
//
// The later case can be found when subclasses manage their own focus scopes.
// For example, ModalRoute create a focus scope in its overlay entries. The
// focused child can only be attached to navigator after initState which
// will be guarded by the asynchronous gap.
TickerFuture
.
complete
().
then
<
void
>((
void
_
)
{
// The route can be disposed before the ticker future completes. This can
// happen when the navigator is under a TabView that warps from one tab to
// another, non-adjacent tab, with an animation. The TabView reorders its
// children before and after the warping completes, and that causes its
// children to be built and disposed within the same frame. If one of its
// children contains a navigator, the routes in that navigator are also
// added and disposed within that frame.
if
(
navigator
?.
widget
.
requestFocus
==
true
)
{
// This TickerFuture serves two purposes. First, we want to make sure
// that animations triggered by other operations will finish before focusing the
// navigator. Second, navigator.focusScopeNode might acquire more focused
// children in Route.install asynchronously. This TickerFuture will wait for
// it to finish first.
//
// Since the reference to the navigator will be set to null after it is
// disposed, we have to do a null-safe operation in case that happens
// within the same frame when it is added.
navigator
?.
focusScopeNode
.
requestFocus
();
});
// The later case can be found when subclasses manage their own focus scopes.
// For example, ModalRoute creates a focus scope in its overlay entries. The
// focused child can only be attached to navigator after initState which
// will be guarded by the asynchronous gap.
TickerFuture
.
complete
().
then
<
void
>((
void
_
)
{
// The route can be disposed before the ticker future completes. This can
// happen when the navigator is under a TabView that warps from one tab to
// another, non-adjacent tab, with an animation. The TabView reorders its
// children before and after the warping completes, and that causes its
// children to be built and disposed within the same frame. If one of its
// children contains a navigator, the routes in that navigator are also
// added and disposed within that frame.
//
// Since the reference to the navigator will be set to null after it is
// disposed, we have to do a null-safe operation in case that happens
// within the same frame when it is added.
navigator
?.
focusScopeNode
.
requestFocus
();
});
}
}
/// Called after [install] when the route replaced another in the navigator.
...
...
@@ -1309,6 +1313,7 @@ class Navigator extends StatefulWidget {
this
.
transitionDelegate
=
const
DefaultTransitionDelegate
<
dynamic
>(),
this
.
reportsRouteUpdateToEngine
=
false
,
this
.
observers
=
const
<
NavigatorObserver
>[],
this
.
requestFocus
=
true
,
this
.
restorationScopeId
,
})
:
assert
(
pages
!=
null
),
assert
(
onGenerateInitialRoutes
!=
null
),
...
...
@@ -1473,6 +1478,12 @@ class Navigator extends StatefulWidget {
/// Defaults to false.
final
bool
reportsRouteUpdateToEngine
;
/// Whether or not the navigator and it's new topmost route should request focus
/// when the new route is pushed onto the navigator.
///
/// Defaults to true.
final
bool
requestFocus
;
/// Push a named route onto the navigator that most tightly encloses the given
/// context.
///
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
76b3c675
...
...
@@ -755,7 +755,7 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
if
(
widget
.
route
.
secondaryAnimation
!=
null
)
widget
.
route
.
secondaryAnimation
!,
];
_listenable
=
Listenable
.
merge
(
animations
);
if
(
widget
.
route
.
isCurrent
)
{
if
(
widget
.
route
.
isCurrent
&&
_shouldRequestFocus
)
{
widget
.
route
.
navigator
!.
focusScopeNode
.
setFirstFocus
(
focusScopeNode
);
}
}
...
...
@@ -764,7 +764,7 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
void
didUpdateWidget
(
_ModalScope
<
T
>
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
assert
(
widget
.
route
==
oldWidget
.
route
);
if
(
widget
.
route
.
isCurrent
)
{
if
(
widget
.
route
.
isCurrent
&&
_shouldRequestFocus
)
{
widget
.
route
.
navigator
!.
focusScopeNode
.
setFirstFocus
(
focusScopeNode
);
}
}
...
...
@@ -792,10 +792,14 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
(
widget
.
route
.
navigator
?.
userGestureInProgress
??
false
);
}
bool
get
_shouldRequestFocus
{
return
widget
.
route
.
navigator
!.
widget
.
requestFocus
;
}
// This should be called to wrap any changes to route.isCurrent, route.canPop,
// and route.offstage.
void
_routeSetState
(
VoidCallback
fn
)
{
if
(
widget
.
route
.
isCurrent
&&
!
_shouldIgnoreFocusRequest
)
{
if
(
widget
.
route
.
isCurrent
&&
!
_shouldIgnoreFocusRequest
&&
_shouldRequestFocus
)
{
widget
.
route
.
navigator
!.
focusScopeNode
.
setFirstFocus
(
focusScopeNode
);
}
setState
(
fn
);
...
...
@@ -1143,7 +1147,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
@override
TickerFuture
didPush
()
{
if
(
_scopeKey
.
currentState
!=
null
)
{
if
(
_scopeKey
.
currentState
!=
null
&&
navigator
!.
widget
.
requestFocus
)
{
navigator
!.
focusScopeNode
.
setFirstFocus
(
_scopeKey
.
currentState
!.
focusScopeNode
);
}
return
super
.
didPush
();
...
...
@@ -1151,7 +1155,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
@override
void
didAdd
()
{
if
(
_scopeKey
.
currentState
!=
null
)
{
if
(
_scopeKey
.
currentState
!=
null
&&
navigator
!.
widget
.
requestFocus
)
{
navigator
!.
focusScopeNode
.
setFirstFocus
(
_scopeKey
.
currentState
!.
focusScopeNode
);
}
super
.
didAdd
();
...
...
packages/flutter/test/widgets/navigator_test.dart
View file @
76b3c675
...
...
@@ -3594,6 +3594,148 @@ void main() {
await
tester
.
pumpWidget
(
Container
(
child:
build
(
key
)));
expect
(
observer
.
navigator
,
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)));
});
testWidgets
(
'Navigator requests focus if requestFocus is true'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
navigatorKey
=
GlobalKey
();
final
GlobalKey
innerKey
=
GlobalKey
();
final
Map
<
String
,
Widget
>
routes
=
<
String
,
Widget
>{
'/'
:
const
Text
(
'A'
),
'/second'
:
Text
(
'B'
,
key:
innerKey
),
};
late
final
NavigatorState
navigator
=
navigatorKey
.
currentState
!
as
NavigatorState
;
final
FocusScopeNode
focusNode
=
FocusScopeNode
();
await
tester
.
pumpWidget
(
Column
(
children:
<
Widget
>[
FocusScope
(
node:
focusNode
,
child:
Container
()),
Expanded
(
child:
MaterialApp
(
home:
Navigator
(
key:
navigatorKey
,
onGenerateRoute:
(
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
BuildContext
_
,
Animation
<
double
>
__
,
Animation
<
double
>
___
)
{
return
routes
[
settings
.
name
!]!;
},
);
},
),
),
),
],
));
expect
(
navigator
.
widget
.
requestFocus
,
true
);
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
);
navigator
.
pushNamed
(
'/second'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'A'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
),
findsOneWidget
);
expect
(
focusNode
.
hasFocus
,
false
);
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
true
);
navigator
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'A'
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
,
skipOffstage:
false
),
findsNothing
);
// Pop does not take focus.
expect
(
focusNode
.
hasFocus
,
true
);
navigator
.
pushReplacementNamed
(
'/second'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'A'
,
skipOffstage:
false
),
findsNothing
);
expect
(
find
.
text
(
'B'
),
findsOneWidget
);
expect
(
focusNode
.
hasFocus
,
false
);
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
true
);
ModalRoute
.
of
(
innerKey
.
currentContext
!)!.
addLocalHistoryEntry
(
LocalHistoryEntry
(),
);
await
tester
.
pumpAndSettle
();
// addLocalHistoryEntry does not take focus.
expect
(
focusNode
.
hasFocus
,
true
);
});
testWidgets
(
'Navigator does not request focus if requestFocus is false'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
navigatorKey
=
GlobalKey
();
final
GlobalKey
innerKey
=
GlobalKey
();
final
Map
<
String
,
Widget
>
routes
=
<
String
,
Widget
>{
'/'
:
const
Text
(
'A'
),
'/second'
:
Text
(
'B'
,
key:
innerKey
),
};
late
final
NavigatorState
navigator
=
navigatorKey
.
currentState
!
as
NavigatorState
;
final
FocusScopeNode
focusNode
=
FocusScopeNode
();
await
tester
.
pumpWidget
(
Column
(
children:
<
Widget
>[
FocusScope
(
node:
focusNode
,
child:
Container
()),
Expanded
(
child:
MaterialApp
(
home:
Navigator
(
key:
navigatorKey
,
onGenerateRoute:
(
RouteSettings
settings
)
{
return
PageRouteBuilder
<
void
>(
settings:
settings
,
pageBuilder:
(
BuildContext
_
,
Animation
<
double
>
__
,
Animation
<
double
>
___
)
{
return
routes
[
settings
.
name
!]!;
},
);
},
requestFocus:
false
,
),
),
),
],
));
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
);
navigator
.
pushNamed
(
'/second'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'A'
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
),
findsOneWidget
);
expect
(
focusNode
.
hasFocus
,
true
);
navigator
.
pop
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'A'
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
,
skipOffstage:
false
),
findsNothing
);
expect
(
focusNode
.
hasFocus
,
true
);
navigator
.
pushReplacementNamed
(
'/second'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'A'
,
skipOffstage:
false
),
findsNothing
);
expect
(
find
.
text
(
'B'
),
findsOneWidget
);
expect
(
focusNode
.
hasFocus
,
true
);
ModalRoute
.
of
(
innerKey
.
currentContext
!)!.
addLocalHistoryEntry
(
LocalHistoryEntry
(),
);
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
true
);
});
}
typedef
AnnouncementCallBack
=
void
Function
(
Route
<
dynamic
>?);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment