Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add high-level documentation and examples on managing keyboard focus. #45076

Closed
mleonhard opened this issue Nov 17, 2019 · 10 comments
Closed

Add high-level documentation and examples on managing keyboard focus. #45076

mleonhard opened this issue Nov 17, 2019 · 10 comments
Assignees
Labels
a: desktop Running on desktop c: new feature Nothing broken; request for a new capability framework flutter/packages/flutter repository. See also f: labels.

Comments

@mleonhard
Copy link
Contributor

Use case

My app must manage focus and the on-screen keyboard. I read the provided documentation:

  • FocusNode class
    • Contains a single example of how to create a widget that can focus/unfocus itself and change its behavior depending on its focus state.
  • Focus class
    • Contains a partial example, which is an excerpt of the FocusNode example.
  • FocusScope class
    • No examples
  • FocusManager class
    • No examples. Documentation explains briefly what it does, but makes no mention of how to use it for anything.
  • FocusScopeNode class
    • No examples.

I found the provided documentation to be missing key information:

  1. How to use Focus and FocusScope to control focus in an application with multiple widgets.
  2. Examples of common use-cases:
    • How to focus a widget in response to a tap.
    • How to automatically focus a new widget.
    • How to unfocus a widget.
    • How to keep a widget focused.
    • How to keep a focused widget visible.
    • How to keep the keyboard on screen.
    • How to determine when the keyboard is visible and update the display accordingly.
    • How to move focus from one widget to the next in a form.
    • How to specify the order of widget focus movement.
    • How to provide expected software keyboard decorations to move from one widget to the next.
    • How to dismiss the software keyboard on non-Android platforms.

My app requires all of these use-cases. So far, I have made-do with reading Flutter's source code (a huge time sink), stackoverflow answers, and snippets shared in Flutter issues. My implementations are unsatisfactory in many ways. Today I have just wasted 2 hours trying to solve the problem of keeping the keyboard on screen on iOS, still with no solution. Focus management is an area where I have sunk a lot of time working around missing parts of Flutter. It has been and continues to be a struggle, a large energy sink. I want to be building features and solving problems for my users, not fighting against the UX framework.

I think better documentation would help me and other app developers to arrive at good solutions quickly.

Proposal

Please allocate some time to writing some high-level documentation on managing keyboard focus in Flutter.

@janmoppel janmoppel added documentation c: new feature Nothing broken; request for a new capability labels Nov 18, 2019
@darrenaustin darrenaustin added the framework flutter/packages/flutter repository. See also f: labels. label Nov 19, 2019
@darrenaustin
Copy link
Contributor

cc @gspencergoog

@gspencergoog gspencergoog added this to To do in Desktop Features Nov 19, 2019
@gspencergoog
Copy link
Contributor

Thanks for filing this with the comprehensive list, @mleonhard. I've added it to our task list, and we will definitely address this.

@goderbauer goderbauer added the a: desktop Running on desktop label Nov 20, 2019
@gspencergoog gspencergoog added this to the January 2020 milestone Dec 18, 2019
@gspencergoog
Copy link
Contributor

I'm working on developing some of the additional docs and examples that are needed, but some of your use cases are currently not supported by Flutter:

Determining when the keyboard is visible and update the display accordingly.

The software keyboard on Android doesn't give any event when it is hidden by the user. The view insets get updated, but it's a generic size update, and we don't have any way to know if it was because of the keyboard disappearing or not. Regardless, we've been able to detect the keyboard state in some apps by looking at the MediaQueryData.viewInsets, because virtually the only reason those change is because a keyboard is visible.

Keeping the keyboard on screen

Keeping the keyboard on the screen if it's already visible is something we should be able to do, but it's not currently supported by the framework. We have a TextInput class and a TextInputConnection that it produces, but the framework currently only has a concept of the focused control having a text input connection, and only one thing can have primary focus at a time, so we'd have to introduce something that holds the text connection open during a transition, for instance.

On Android, it's not possible to force the keyboard to be shown, since the user can dismiss the keyboard.

Dismissing the software keyboard on non-Android platforms.

Yes, iOS does have a way to dismiss the keyboard, but we don't have an API for it yet.

Some of these related issues might be of interest to you:
#46882
#44681

@gspencergoog
Copy link
Contributor

For "How to use Focus and FocusScope to control focus in an application with multiple widgets.", what kinds of control are you referring to? Here's what I was going to start with:

  • Restricting focus to a set of widgets (FocusScope)
  • Switching focus between two widgets using another widget.

But your request is somewhat general, so I'm not sure what specifics you were missing, or use cases you wanted to see and didn't find.

@gspencergoog
Copy link
Contributor

@mleonhard, when you say "How to provide expected software keyboard decorations to move from one widget to the next.", are you referring to the "->" (next) button on the software keyboards, or something else?

@mleonhard
Copy link
Contributor Author

Greg, Thank you for taking a look at this issue.

"How to use Focus and FocusScope to control focus in an application with multiple widgets."

Let me share my concrete use cases. Our app has a sign up screen with a phone number text field, a "Send Verification Code" button, and a "verification code" text field.

  1. When the phone number text field is selected, the app scrolls the list down far enough to see the "Send Verification Code" button. I found nothing about how to do this in the Focus* or ListView pages. A web search yielded a StackOverflow answer which points to Scrollable.ensureVisible(). Our app is currently using the TextField.scrollPadding property which has drawbacks. This information would be a good addition to the Flutter docs.
  2. When the "Send Verification Code" button is pressed, after successful RPC, the app focuses the "verification code" text field. The example code in FocusNode shows how a widget can focus and unfocus itself. This isn't a very useful or common use-case. I need to know how to focus another widget. Again, I found answers with StackOverflow. And the results are ugly. I don't know how to use FocusAttachment or if it's even necessary. My solution is to create a StatefulWidget that holds all of the focus-able widgets. This StatefulWidget contains a FocusNode object for each focus-able widget, created in initState() and disposed in dispose(). It passes these FocusNode objects to the widgets. It ends up being ugly spaghetti code. It would be nice to have a clean example. Even better, would be a focus(Key) or focusNext() method.

"How to provide expected software keyboard decorations to move from one widget to the next."

Yes, this is the bar that appears in iOS above the keyboard. I would like to see a CupertinoKeyboardUIToolbar widget which appears and disappears automatically. The widget could support custom buttons and also have built-in support for '⌃', '⌄', and 'Done' buttons.

We need a way to implement '⌃' and '⌄' buttons. This will require a way to specify an ordered set of focusable widgets. How about focusNext(List<Key>) and focusPrevious(List<Key>) methods?

The 'Done' button is important since #21814 is still not fixed.

I would like a documented convention for which component is responsible for scrolling a focused widget into view, one of: the widget receiving focus, the code that changes focus, the enclosing scrollable container, the Flutter Focus* objects, or something else.

@gspencergoog
Copy link
Contributor

gspencergoog commented Jan 22, 2020

@mleonhard , for focusing other widgets, I can see that could be an issue if you have to jump around a lot. Is the reason you have to focus other widgets directly because you can't control the focus traversal order easily/well enough? I envisioned that most of the time in this kind of case you would just call Focus.of(context).nextFocus() or DefaultFocusTraversal.of(context).next(primaryFocus) for most cases like you describe.

FYI, I've just submitted #49235 for review, which allows definition of an explicit order for widgets easily, and refactors the FocusTraversalPolicy so that it's easier to make your own policy (you just have to define sortDescendants in most cases).

If you want to focus widgets based on a global key, you can do that, but for things that have their Focus widget built in (like buttons), you'll need to use a global key applied to their children, not to the widget directly (because otherwise we'd have to search down in the hierarchy, which would be too slow). It would look like this:

GlobalKey myGlobalKey = GlobalKey():
...
Widget build(BuildContext context) {
  return FlatButton(child: Text("Click Me", key: myGlobalKey), onPressed: () {});
}
...
// somewhere else
Focus.of(myGlobalKey.currentContext).requestFocus();

I'll add an example of that. The downside of using GlobalKey like that is that 1) GlobalKey can be expensive, and it also affects how widgets are re-used, and 2) that you then have to keep track of the global keys somewhere just as you did the focus nodes. Using a LocalKey isn't really an option either, since we'd have to search the hierarchy for it, which is O(n log(n)).

Also, you shouldn't need to use FocusAttachment, pretty much ever. There are only two classes in all of Flutter that use it, and one of those is the Focus widget itself (the other is EditableText). It's only needed if you're building your own version of the Focus widget, or something like it. I'll add a comment for that on FocusAttachment.

@gspencergoog
Copy link
Contributor

As for what has responsibility for scrolling something into view (calling Scrollable.ensureVisible), I'd say that the right thing is the code that changes focus. For instance, with focus traversal, the focus traversal code will cause the focused item to be visible. If your code explicitly requests focus, then you need to call ensureVisible yourself.

@Hixie Hixie modified the milestones: January 2020, Overdue Feb 12, 2020
@gspencergoog gspencergoog modified the milestones: Overdue, May 2020 Feb 12, 2020
@gspencergoog gspencergoog moved this from To do to In progress in Desktop Features Feb 13, 2020
@gspencergoog
Copy link
Contributor

I think most of these are addressed now. I'm going to close this, and if you have specific additional issues, please file them separately so we can prioritize appropriately. Thanks for reporting this!

@gspencergoog gspencergoog moved this from In progress to Done in Desktop Features Apr 10, 2020
@lock
Copy link

lock bot commented Apr 25, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@lock lock bot locked and limited conversation to collaborators Apr 25, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: desktop Running on desktop c: new feature Nothing broken; request for a new capability framework flutter/packages/flutter repository. See also f: labels.
Projects
Development

No branches or pull requests

6 participants