Skip to content

Conversation

@QuncCccccc
Copy link
Contributor

@QuncCccccc QuncCccccc commented Dec 22, 2022

Fixes #116587

This PR is to add requestFocusOnTap for DropdownMenu.
By default, if the app is running on mobile platforms, the text input field will not request focus when the menu is open. If the app is running on desktop platforms, the text input field will request focus when the menu is open. When setting requestFocusOnTap to false and tapping the text field, the text field will remain unfocused, and the virtual keyboard on mobile platforms will not appear. Setting requestFocusOnTap to false is typically useful when users only have limited options in the menu list and searching is unnecessary.

The first DropdownMenu is the default DropdownMenu behavior. The second one set requestFocusOnTap to true.

Screen.Recording.2023-01-26.at.12.46.55.AM.mov

This PR also adds some small style change to make sure the trailing icon button has a circular highlight when it is clicked and removes textEditingController.dispose() because I don't see the dispose method in TextEditingController.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos documentation f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. c: contributor-productivity Team-specific productivity, code health, technical debt. labels Dec 22, 2022
@QuncCccccc QuncCccccc marked this pull request as ready for review December 22, 2022 01:24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving the keyboard focus to a Dropdown menu on mobile doesn't seem like the common case, so maybe we should make this a nullable property and compute a default value (if needed) based on the target platform. This would be an incompatible change however DropdownMenu hasn't been out for long.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! On mobile platforms, the text field will be focusable only when the showVirtualKeyboard is true now. Thanks so much for the suggestion. Please let me know if there're any problems:)

@QuncCccccc QuncCccccc force-pushed the add_flag_to_control_virtual_keyboard_on_dropdown_menu branch from 4a1170a to 2644834 Compare January 3, 2023 19:37
Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, just want to maybe tweak the showVirtualKeyboard flag a little.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of showVirtualKeyboard maybe this should be takesFocus, since one might set it to false on desktop platforms to prevent searching or filtering?

@gspencergoog - do we have a conventional name for a flag that means "request the focus when clicked or tapped"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don't have a set name for that, since text fields are the only thing that do that.

takesFocus doesn't include the part about needing user interaction to take focus, it sounds like a synonym for canRequestFocus which is what we currently use for the ability to take focus.

Maybe requestFocusOnTap instead?

But the docs here say "If set to true ... the text field is focusable", implying that if it is set to false, then the text field is not focusable, which I don't think is the case, correct? It's still focusable, it just doesn't request focus on tap, meaning that you could tab to get to it, and it would focus, you just can't tap on it to focus it, am I right?

Also, it seems odd to have it be ignored on some platforms and not others. It seems like desktop platforms could just default to false, and mobile default to true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestFocusOnTap sounds better and agreed that the default value would be false for mobile, true otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don't have a set name for that, since text fields are the only thing that do that.

takesFocus doesn't include the part about needing user interaction to take focus, it sounds like a synonym for canRequestFocus which is what we currently use for the ability to take focus.

Maybe requestFocusOnTap instead?

But the docs here say "If set to true ... the text field is focusable", implying that if it is set to false, then the text field is not focusable, which I don't think is the case, correct? It's still focusable, it just doesn't request focus on tap, meaning that you could tab to get to it, and it would focus, you just can't tap on it to focus it, am I right?

Also, it seems odd to have it be ignored on some platforms and not others. It seems like desktop platforms could just default to false, and mobile default to true.

requestFocusOnTap sounds good to me! Thanks for the suggestion. And yes, I think "focusable" here is not accurate. I need to change the doc.

And will set the default value to false for mobile platforms and true for desktop platforms. Thanks!

@gspencergoog
Copy link
Contributor

Actually, in thinking about it some more, it seems to me that this PR isn't the right solution: a field that doesn't allow the text field to be focused really shouldn't use a text field at all, since it doesn't allow editing, and doesn't allow focus. It's much more of just a label showing the currently selected value of the dropdown.

In the first field in the video, it looks to me like a cursor appears in the field after selection and that seems inconsistent, since there are only a fixed number of allowed values, and you can't search, right?

Could this instead just be a different kind of field with a different constructor?

Or maybe I'm just misunderstanding the issue. Does it allow searching if it doesn't allow focus? Can you search only after you select something (which would seem odd)?

@QuncCccccc
Copy link
Contributor Author

Actually, in thinking about it some more, it seems to me that this PR isn't the right solution: a field that doesn't allow the text field to be focused really shouldn't use a text field at all, since it doesn't allow editing, and doesn't allow focus. It's much more of just a label showing the currently selected value of the dropdown.

Yeah, it is a little odd that a text field cannot be edited. By default, the ComboBox should be able to search/filter and I think this flag is more like a small feature to turn off these interactions. Does this make sense?

In the first field in the video, it looks to me like a cursor appears in the field after selection and that seems inconsistent, since there are only a fixed number of allowed values, and you can't search, right?

Ah that's the selection handle, not a cursor. Seems that I forgot to set the enableInteractiveSelection to false when requestFocusOnTap is false. NICE catch!! Will fix it. Thanks!

Could this instead just be a different kind of field with a different constructor?

Or maybe I'm just misunderstanding the issue. Does it allow searching if it doesn't allow focus? Can you search only after you select something (which would seem odd)?

No, I think your understanding is totally correct. If it is unfocused, then no interactions at all (no searching/editing). It should only be able to make a selection.

@HansMuller
Copy link
Contributor

I think the approach taken here is a good one because the same code will work well, for the common case, on both mobile and desktop. If we add a separate constructor for the requestFocusOnTap:false case, then developers will have to write different code on different platforms (for the common case), which is undesirable. With the code as it is, the implementation could eventually just create a Text widget instead of a TextField, when requestFocusOnTap is false.

@gspencergoog
Copy link
Contributor

If we add a separate constructor for the requestFocusOnTap:false case, then developers will have to write different code on different platforms ...

This is a good point. OK, let's proceed with the parameter, then.

@QuncCccccc QuncCccccc requested a review from HansMuller January 6, 2023 18:21
Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just need to deal with the complexity of the focus node's lifetime.

I think @gspencergoog might have an idiomatic way of dealing with a disposable property that might be passed in (so leave it alone) or auto-created (so dispose it).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably doesn't bear mentioning, but: the widget will only create a (private) FocusNode if this parameter is null and requestFocusOnTap is true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// keyboard shows on a touch event.
/// keyboard is shown in response to a touch event.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// will show.
/// will be shown.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// If this is null or false, the text field will not request focus when the menu is open,
/// If this is null or false, the text field will not request focus when the menu is opened,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// By default, this is true on desktop platforms, and false on mobile platforms.
/// By default, this property is true on desktop platforms and false on mobile platforms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky. If the focusNode was provided (via widget.focusNode) then we should not dispose it. If requestFocusOnTap is false, then we probably don't need to create a focus node. If the dropdown gets rebuilt with a new value of focusNode or requestFocusOnTap we'll need to update things in didUpdateWidget. For example, the widget might be built with widget.focusNode:null, and then rebuilt with a non-null focus node. A bunch of tests to make sure we do the right thing will be needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will update didUpdateWidget and tests. Thanks a lot!

But for this condition,

If requestFocusOnTap is false, then we probably don't need to create a focus node.

Currently, if the requestFocusOnTap is false, I use the focusNode to unfocus (_textFocusNode.unfocus();) the text field when it is tapped. Is this the correct way to keep the TextField unfocused? It looks like we don't need to create a focus node when requestFocusOnTap is true because the text field will automatically request focus when it's tapped.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably shouldn't unfocus the text field like that, since there are edge cases where you'll have it flashing as focused for a frame, or having to unfocus at times when you didn't expect to (e.g. when popping a route).

The long term answer is probably to use a Text widget instead of a TextField when it is not focusable. That is much easier said than done, however, since the Text widget doesn't have an InputDecorator, and we probably want the decoration, and there are a lot of things that the text field does for you here.

In the meantime, though, would setting the readOnly attribute on the text field be sufficient? Probably not: seems like that's kind of what you want, but I think that it ignores taps then, which might not be ideal.

Another alternative would be to always supply a focus node to the text field, and set canRequestFocus on the node to be false. Ugh. No, that won't work, because it is overwritten by the text field, and it only turns it off if the field is disabled.

Okay, how about adding an optional canRequestFocus to the text field, and having that override the text field's internal logic if it is set. Then you can pass "false" for that attribute to the text field, and it won't be allowed to accept focus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds good to me! Will make the change. Thanks so much:)

@gspencergoog
Copy link
Contributor

The idiomatic way to handle the case where you have an optional passed-in focus node, but need to have one always available is to do something like what is done on MenuItemButton, where there's an internal focus node used if one isn't given. It's not concise, but I haven't been able to think of a way to make it into a mixin or something that wasn't just as verbose.

@QuncCccccc
Copy link
Contributor Author

The idiomatic way to handle the case where you have an optional passed-in focus node, but need to have one always available is to do something like what is done on MenuItemButton, where there's an internal focus node used if one isn't given. It's not concise, but I haven't been able to think of a way to make it into a mixin or something that wasn't just as verbose.

I see. Will fix this part!

@QuncCccccc QuncCccccc force-pushed the add_flag_to_control_virtual_keyboard_on_dropdown_menu branch from 779147f to eb10201 Compare January 20, 2023 02:07
@QuncCccccc QuncCccccc changed the title Add focusNode and showVirtualKeyboard properties Add requestFocusOnTap to DropdownMenu Jan 26, 2023
@QuncCccccc QuncCccccc force-pushed the add_flag_to_control_virtual_keyboard_on_dropdown_menu branch from eb10201 to fb61270 Compare January 26, 2023 08:37
@flutter-dashboard flutter-dashboard bot added the a: text input Entering text in a text field or keyboard related problems label Jan 26, 2023
@QuncCccccc QuncCccccc requested review from HansMuller and gspencergoog and removed request for darrenaustin January 26, 2023 17:17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Defaults to true. Set to false if you want the [TextField] manages to do
/// Defaults to true. If false, the text field will not request focus when tapped, or when its context menu is displayed. If false it will not be possible to move the focus to the text field with tab key.

Not sure if I've got this all right. Can the developer still give the text field the focus by having its focus node request the focus? ... it looks like the answer is "no"; we should clear up that case here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. The text field cannot be focused if false. Sorry for missing this part.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the padding style for the trailing icon button from EdgeInsets.symmetric(horizontal: 4.0) to EdgeInsets.all(4.0) for some vertical space, so the height of the text field gets changed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What 'done' button?

This should read something like: the desktop platform closed the menu when .

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about the confusion. It should be the done action when editing is finished. Just updated this comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requestFocusOnTap parameter doesn't appear to be needed here. Hopefully we have a test that proves that one can override the default value of requestFocus for mobile (to true) and still get the dropdown menu.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the parameter! The last test is to override the default value for all platforms to check if the dropdown menu still works. Just added some more checks in that test to make sure we can make a selection when requestFocusOnTap is true for mobile or requestFocusOnTap is false for desktop.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only use firstWidget when more than one widget is expected and the first one is the one we want. Otherwise tester.widget

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
testWidgets('If the requestFocusOnTap is true, the text input field can request focus, '
testWidgets('If requestFocusOnTap is true, the text input field can request focus, '

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably variant: TargetPlatformVariant.all() is the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to test that the dropdown menu works on all platforms when a value of requestFocusOnTap is provided.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should verify that the focus doesn't change if the text field is tapped or its context menu is shown (long press or right-button on desktop).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more tests.

Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting this field to true or false, rather than allowing the implementation to choose based on the platform, can be useful for applications that want to override the default behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, on mobile platforms, tapping on the text field and opening the menu will not case a focus request and the virtual keyboard will not appear. The default behavior for desktop platforms is for the dropdown to take the focus.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it doesn't matter very much, this would probably be. slightly better with a short circuit return:

    if (widget.requestFocusOnTap != null) {
      return widget.requestFocusOnTap;
    }
    switch (Theme.of(context).platform) {
      case TargetPlatform.iOS:
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return false
        break;
      case TargetPlatform.macOS:
      case TargetPlatform.linux:
      case TargetPlatform.windows:
        return true;
    }

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 29, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 29, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 30, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 30, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 30, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 30, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 31, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 31, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 31, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 31, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 31, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 31, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jan 31, 2023
@KiddoV
Copy link

KiddoV commented Jun 13, 2025

Am I missing something here?
When set requestFocusOnTap: false, I still see the text field being focus. 🤷🏼‍♂️

@QuncCccccc
Copy link
Contributor Author

@KiddoV Could you provide some code sample? I wonder if there's some changes after this PR broke the behavior. Maybe @bleroux have some more context?

@bleroux
Copy link
Contributor

bleroux commented Jun 18, 2025

@justinmc who tried hard to land a related fix, see #149067.
Several DropdownMenu PRs landed to fix issues related to focus, maybe one broke the initial behavior.
@KiddoV Can you file a new issue with some details (code sample, video recording) ? It will be easier to track.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: text input Entering text in a text field or keyboard related problems autosubmit Merge PR when tree becomes green via auto submit App c: contributor-productivity Team-specific productivity, code health, technical debt. d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DropdownMenu] Disable text input

5 participants