Unverified Commit 18f8002e authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Make FocusManager a ChangeNotifier that notifies when the prim… (#50392)

This makes FocusManager a ChangeNotifier that notifies when the primaryFocus changes so that an app can track the current focus.

Fixes #50501
parent 9cb2953e
......@@ -1166,6 +1166,15 @@ enum FocusHighlightStrategy {
/// current focus in that [FocusScopeNode] is null, it will stop there, and no
/// [FocusNode] will have focus.
///
/// If you would like notification whenever the [primaryFocus] changes, register
/// a listener with [addListener]. When you no longer want to receive events, as
/// when your object is about to be disposed, you must unregister with
/// [removeListener] to avoid memory leaks. Removing listeners is typically done
/// in [State.dispose] on stateful widgets.
///
/// The [highlightMode] changes are notified separately via
/// [addHighlightModeListener] and removed with [removeHighlightModeListener].
///
/// See also:
///
/// * [FocusNode], which is a node in the focus tree that can receive focus.
......@@ -1177,7 +1186,7 @@ enum FocusHighlightStrategy {
/// a given [BuildContext].
/// * The [focusManager] and [primaryFocus] global accessors, for convenient
/// access from anywhere to the current focus manager state.
class FocusManager with DiagnosticableTreeMixin {
class FocusManager with DiagnosticableTreeMixin, ChangeNotifier implements Diagnosticable {
/// Creates an object that manages the focus tree.
///
/// This constructor is rarely called directly. To access the [FocusManager],
......@@ -1443,6 +1452,9 @@ class FocusManager with DiagnosticableTreeMixin {
node._notify();
}
_dirtyNodes.clear();
if (previousFocus != _primaryFocus) {
notifyListeners();
}
assert(() {
if (_kDebugFocus) {
debugDumpFocusTree();
......
......@@ -745,4 +745,92 @@ void main() {
await tester.pump();
expect(parent1.focusedChild, equals(child2));
});
testWidgets('Focus changes notify listeners.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1');
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
final FocusAttachment child2Attachment = child2.attach(context);
parent1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
child1Attachment.reparent(parent: parent1);
child2Attachment.reparent(parent: child1);
int notifyCount = 0;
void handleFocusChange() {
notifyCount++;
}
tester.binding.focusManager.addListener(handleFocusChange);
parent1.autofocus(child2);
expect(notifyCount, equals(0));
await tester.pump();
expect(notifyCount, equals(1));
notifyCount = 0;
child1.requestFocus();
child2.requestFocus();
child1.requestFocus();
await tester.pump();
expect(notifyCount, equals(1));
notifyCount = 0;
child2.requestFocus();
await tester.pump();
expect(notifyCount, equals(1));
notifyCount = 0;
child2.unfocus();
await tester.pump();
expect(notifyCount, equals(1));
notifyCount = 0;
tester.binding.focusManager.removeListener(handleFocusChange);
});
testWidgets('FocusManager notifies listeners when a widget loses focus because it was removed.', (WidgetTester tester) async {
final FocusNode nodeA = FocusNode(debugLabel: 'a');
final FocusNode nodeB = FocusNode(debugLabel: 'b');
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: Column(
children: <Widget>[
Focus(focusNode: nodeA , child: const Text('a')),
Focus(focusNode: nodeB, child: const Text('b')),
],
),
),
);
int notifyCount = 0;
void handleFocusChange() {
notifyCount++;
}
tester.binding.focusManager.addListener(handleFocusChange);
nodeA.requestFocus();
await tester.pump();
expect(nodeA.hasPrimaryFocus, isTrue);
expect(notifyCount, equals(1));
notifyCount = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: Column(
children: <Widget>[
Focus(focusNode: nodeB, child: const Text('b')),
],
),
),
);
await tester.pump();
expect(nodeA.hasPrimaryFocus, isFalse);
expect(nodeB.hasPrimaryFocus, isFalse);
expect(notifyCount, equals(1));
notifyCount = 0;
tester.binding.focusManager.removeListener(handleFocusChange);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment