Repository navigation
Can't apply TapRegion to a dropdown #128317
Description
Is there an existing issue for this?
- I have searched the existing issues
- I have read the guide to filing a bug
Use case
The TapRegion widget is great for detecting taps that occur either inside or outside a region. These regions are defined such that tapping on child widgets is considered to be inside the region of interest.
A child widget such as DropDownButton is only inside the region when tapped to activate. Once the dropdown is displayed any tap to either select or dismiss is considered as outside the region. Yes, the implementation of the dropdown is a popup with a barrier that are not rendered as children. Conceptually though, the dropdown is contained within the region and the entire behavior should be treated as inside.
Here is the DartPad "Counter Example" build method extended with a TapRegion and DropDownButton. Activating the dropdown is considered inside the region as can be seen in the console log. Tapping on the activated dropdown to select or dismiss is outside.
Code sample
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
// Any "taps" in any widget in the column is considered "inside" the region
child: TapRegion(
onTapInside: (_) => print("Tapped inside"),
onTapOutside: (_) => print("Tapped OUTSIDE"),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
// This button is "inside"
IconButton(icon: const Icon(Icons.add), onPressed: _incrementCounter),
// Activating the dropdown is "inside"
// However all taps on the dropdown menu/barrier are "outside"
DropdownButton(
items: [1,2,3].map((n) => DropdownMenuItem(
value: n, child: Text("Item $n"))).toList(),
onChanged: (c) => print("Dropdown pick $c")
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}TapRegion has a groupId parameter that allows various regions to be conceptually combined. However, DropDownButton provides no ability to access or augment the popup and barrier.
Proposal
Ideally, Flutter would recognize when widgets such as dropdowns and popups are contained in a TapRegion and extend that region to the resulting implementation that is not rendered as a child.
Alternatively, these widgets could expose some mechanism to allow the developer to augment the popup and barrier with a region groupId.
This request doesn't appear unprecedented. Flutter already includes a TextFieldTapRegion widget that extends the tap region for text editing.
Activity
NicolasEymael commentedon Jan 5, 2024
any news on this?
lewinpauli commentedon Jun 18, 2024
any updates on this?
flutter-triage-bot commentedon Jul 8, 2024
This issue is missing a priority label. Please set a priority label when adding the
triaged-designlabel.Piinks commentedon Jul 11, 2024
FYI @gspencergoog is this intended behavior?
gspencergoog commentedon Jul 11, 2024
Yes, it's intended behavior for the
TapRegionwidget.But I'm not sure what the intended behavior should be for the dropdown widget. I think the dropdown is adding a modal barrier, which blocks any other events from getting a look in. The dropdown itself doesn't use a
TapRegion.Implementing this as described would be hard to do, since there are a lot of edge cases that could occur (e.g. what if the dropdown is surrounded by two TapRegions? Do you duplicate both in the overlay/modal route?), and it feels like it's overly complicated.
Also, what's a specific use case here? There already is a modal route for the dropdown, so it's not about the usual use for a
TapRegion, which is getting the dropdown to go away when clicking somewhere else, since that will already happen.Could your desired result be obtained by wrapping the child of each item in a
TapRegionwith the samegroupIdas the one you put around the button?rich-j commentedon Jul 12, 2024
Yes, except we're not aware of a supported way to get access to a popup/dropdown child/barrier to add a
TapRegion groupId.We currently create an overlay with the
TapRegion groupIdthat we need to explicitly open when these controls are activated and explicitly close when the controls are no longer active. However some native Flutter controls, such as DropdownButton, don't provide the necessary functionality - there is no way to know when the dropdown/barrier is active.Here are a few examples of what we can get working:
Code sample
The flutter PopupMenuButton provides the events
onOpenedandonCanceledthat we use to create a full page overlay with theTapRegion.For the dropdown functionality, we use the user contributed DropdownButton2 that provides the event
onMenuStateChange.Here is our implementation of the overlay that includes the
TapRegion:One downside to the above implementation is there is a U/I flow that doesn't fire the expected events, so we need to brute force remove the overlay (sorry, I don't recall the details). As suggested by @gspencergoog, it would be better to wrap/augment the children, particularly the barrier, with the
TapRegion groupId- this would avoid the need to create the overlay and would go away when the barrier does.We try to minimize third-party dependencies and would prefer to use the Flutter dropdown instead of the contributed
DropdownButton2that does provide needed events. The Flutter dropdown would need to provide notification of the popup opening and closing.As for one of our "use cases" (which you may call non-standard but works very well in our app and our users love it) we provide a "quick edit" mode that allows a few simple edits to list items. When the "quick edit" mode is entered we create a visual editing area with a
TapRegionthat allows changes to be made to a few values and when the area is exited (tapping outside or focus tabbing) the area is closed and changes committed. Text fields, radio buttons, checkboxes all work well. Our problem are popup/dropdown, it's visually and conceptually in theTapRegion, however when activated the resulting barrier/popup is not.The "ideal" solution is that a popup/dropdown just works when the base control is in one or many
TapRegions - the resulting popup/barrier would inherit the region(s). However, we recognize thatTapRegionuse is somewhat specialized and that the developer can add the region to the popup/barrier if Flutter native controls would provide the means to do so.gspencergoog commentedon Jul 12, 2024
So, would adding a callback on closing the popup solve your issue? If it's that simple, I don't see a problem with doing that.
If your users want it, and it's useful, then it doesn't matter if it's non-standard or not: it is still a valid use case.
Yes, I can see that would help your implementation. I wonder if the right solution to this would be to convert the implementation of the
DropdownButtonto useOverlayPortalinstead of theOverlaydirectly. That would give the illusion to the widgets in the menu, including the panel, that they were descendants of the button in the widget tree, and so also descendants of theTapRegionyou put around it. The down side of that is that it would require a pretty huge change to all thePopupRoutedependent things in the SDK. This would be a lot of work, so I wouldn't count on it any time soon, but we could file an issue for that. It might be a good change to make in general, for a number of other use cases as well.gspencergoog commentedon Jul 12, 2024
I filed #151671 as a proposal for the long term solution. As I said, though, that might take quite a while.
rich-j commentedon Jul 25, 2024
Yes, knowing when the dropdown popup opens (already available) and closes would allow us to use the Flutter dropdown instead of a contributed one. When the dropdown is "active" with a popup, a click anywhere dismisses the popup - thus we create a full screen overlay that extends the TapRegion to make such click as in-region. This overlay must be removed when the popup is dismissed - thus the need for a close callback.
Thank you. This is the preferred solution - widgets (e.g. dropdown) that temporarily extend beyond a TapRegion during user interaction should still be considered as "in-region".
soufianebenyaala commentedon Nov 12, 2024