|
| 1 | +--- |
| 2 | +title: TextField FocusNode attach location change |
| 3 | +description: EditableText.focusNode is no longer attached to EditableTextState's BuildContext |
| 4 | +--- |
| 5 | + |
| 6 | +## Summary |
| 7 | + |
| 8 | +`EditableText.focusNode` is now attached to a dedicated `Focus` widget below |
| 9 | +`EditableText`. |
| 10 | + |
| 11 | +## Context |
| 12 | + |
| 13 | +A text input field widget (`TextField`, for example) typically owns a `FocusNode`. |
| 14 | +When that `FocusNode` is the primary focus of the app, events (such as key presses) |
| 15 | +are sent to the `BuildContext` to which the `FocusNode` is attached. |
| 16 | + |
| 17 | +The `FocusNode` also plays a roll in shortcut handling: The `Shortcuts` widget |
| 18 | +translates key sequences into an `Intent`, and tries to find the first suitable |
| 19 | +handler for that `Intent` starting from the `BuildContext` to which the `FocusNode` |
| 20 | +is attached, to the root of the widget tree. This means an `Actions` widget (that provides |
| 21 | +handlers for different `Intent`s) won't be able to handle any shortcut `Intent`s |
| 22 | +when the `BuildContext` that has the primary focus is above it in the tree. |
| 23 | + |
| 24 | +Previously for `EditableText`, the `FocusNode` was attached to the `BuildContext` |
| 25 | +of `EditableTextState`. Any `Actions` widgets defined in `EditableTextState` (which |
| 26 | +will be inflated below the `BuildContext` of the `EditableTextState`) couldn't |
| 27 | +handle shortcuts even when that `EditableText` was focused, for the reason stated |
| 28 | +above. |
| 29 | + |
| 30 | +## Description of change |
| 31 | + |
| 32 | +`EditableTextState` now creates a dedicated `Focus` widget to host `EditableText.focusNode`. |
| 33 | +This allows `EditableTextState`s to define handlers for shortcut `Intent`s. For |
| 34 | +instance, `EditableText` now has a handler that handles the "deleteCharacter" intent |
| 35 | +when the <kbd>DEL<kbd> key is pressed. |
| 36 | + |
| 37 | +This change does not involve any public API changes but breaks codebases relying on |
| 38 | +that particular implementation detail to tell if a `FocusNode` is associated with a |
| 39 | +text input field. |
| 40 | + |
| 41 | +This change does not break any builds but can introduce runtime issues, or |
| 42 | +cause existing tests to fail. |
| 43 | + |
| 44 | +## Migration guide |
| 45 | + |
| 46 | +The `EditableText` widget takes a `FocusNode` as a paramter, which was |
| 47 | +previously attached to its `EditableText`'s `BuildContext`. If you are relying |
| 48 | +on runtime typecheck to find out if a `FocusNode` is attached to a text input |
| 49 | +field or a selectable text field like so: |
| 50 | + |
| 51 | +- `focusNode.context.widget is EditableText` |
| 52 | +- `(focusNode.context as StatefulElement).state as EditableTextState` |
| 53 | + |
| 54 | +Then please read on and consider following the migration steps to avoid breakages. |
| 55 | + |
| 56 | +If you're not sure whether a codebase needs migration, search for `is EditableText`, |
| 57 | +`as EditableText`, `is EditableTextState`, and `as EditableTextState` and verify if |
| 58 | +any of the search results are doing a typecheck or typecast on a `FocusNode.context`. |
| 59 | +If so, then migration is needed. |
| 60 | + |
| 61 | +To avoid performing a typecheck, or downcasting the `BuildContext` associated with the |
| 62 | +`FocusNode` of interest, and depending on the actual capabilities the codebase is |
| 63 | +trying to invoke from the given `FocusNode`, fire an `Intent` from that |
| 64 | +`BuildContext`. For instance, if you wish to update the text of the currently focused |
| 65 | +`TextField` to a specific value, see the following example: |
| 66 | + |
| 67 | +Code before migration: |
| 68 | + |
| 69 | +<!-- skip --> |
| 70 | +```dart |
| 71 | +final Widget? focusedWidget = primaryFocus?.context?.widget; |
| 72 | +if (focusedWidget is EditableText) { |
| 73 | + widget.controller.text = 'Updated Text'; |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +Code after migration: |
| 78 | + |
| 79 | +<!-- skip --> |
| 80 | +```dart |
| 81 | +final BuildContext? focusedContext = primaryFocus?.context; |
| 82 | +if (focusedContext != null) { |
| 83 | + Actions.maybeInvoke(focusedContext, ReplaceTextIntent('UpdatedText')); |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +For a comprehensive list of `Intent`s supported by the `EditableText` widget, |
| 88 | +refer to the documentation of the `EditableText` widget. |
| 89 | + |
| 90 | +## Timeline |
| 91 | + |
| 92 | +Landed in version: 2.6.0-12.0.pre<br> |
| 93 | +In stable release: not yet |
| 94 | + |
| 95 | +## References |
| 96 | + |
| 97 | +API documentation: |
| 98 | + |
| 99 | +* [`EditableText`][] |
| 100 | + |
| 101 | +Relevant PRs: |
| 102 | + |
| 103 | +* [Move text editing Actions to EditableTextState][] |
| 104 | + |
| 105 | +[`EditableText`]: {{site.master-api}}/flutter/widgets/EditableText-class.html |
| 106 | +[Move text editing Actions to EditableTextState]: {{site.github}}/flutter/flutter/pull/90684 |
| 107 | + |
0 commit comments