Skip to content

Commit 0abb017

Browse files
authored
Reland: "Add code for updating focusedChild when removing grandchildren from scope" (#136899)
1 parent f82299e commit 0abb017

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

packages/flutter/lib/src/widgets/focus_manager.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,13 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
964964
assert(node._manager == _manager);
965965

966966
if (removeScopeFocus) {
967-
node.enclosingScope?._focusedChildren.remove(node);
967+
final FocusScopeNode? nodeScope = node.enclosingScope;
968+
if (nodeScope != null) {
969+
nodeScope._focusedChildren.remove(node);
970+
node.descendants.where((FocusNode descendant) {
971+
return descendant.enclosingScope == nodeScope;
972+
}).forEach(nodeScope._focusedChildren.remove);
973+
}
968974
}
969975

970976
node._parent = null;

packages/flutter/test/widgets/focus_manager_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,46 @@ void main() {
459459
expect(child2.hasPrimaryFocus, isTrue);
460460
});
461461

462+
// Regression test for https://github.com/flutter/flutter/issues/136758
463+
testWidgetsWithLeakTracking('removing grandchildren from scope updates focusedChild', (WidgetTester tester) async {
464+
final BuildContext context = await setupWidget(tester);
465+
466+
// Sets up this focus node tree:
467+
//
468+
// root
469+
// |
470+
// scope1
471+
// |
472+
// child1
473+
// |
474+
// child2
475+
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope2');
476+
addTearDown(scope1.dispose);
477+
final FocusAttachment scope2Attachment = scope1.attach(context);
478+
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
479+
480+
final FocusNode child1 = FocusNode(debugLabel: 'child2');
481+
addTearDown(child1.dispose);
482+
final FocusAttachment child2Attachment = child1.attach(context);
483+
484+
final FocusNode child2 = FocusNode(debugLabel: 'child3');
485+
addTearDown(child2.dispose);
486+
final FocusAttachment child3Attachment = child2.attach(context);
487+
488+
child2Attachment.reparent(parent: scope1);
489+
child3Attachment.reparent(parent: child1);
490+
expect(child1.parent, equals(scope1));
491+
expect(scope1.children.first, equals(child1));
492+
child2.requestFocus();
493+
await tester.pump();
494+
expect(scope1.focusedChild, equals(child2));
495+
496+
// Detach the middle child and make sure that the scope is updated so that
497+
// it no longer references child2 as the focused child.
498+
child2Attachment.detach();
499+
expect(scope1.focusedChild, isNull);
500+
});
501+
462502
testWidgetsWithLeakTracking('Requesting focus before adding to tree results in a request after adding', (WidgetTester tester) async {
463503
final BuildContext context = await setupWidget(tester);
464504
final FocusScopeNode scope = FocusScopeNode();

0 commit comments

Comments
 (0)