Skip to content

Conversation

@justinmc
Copy link
Contributor

@justinmc justinmc commented Feb 9, 2024

Problem Before After
Width Screenshot 2024-02-09 at 1 29 09 PM Screenshot 2024-02-09 at 1 23 59 PM
Overflow Screenshot from 2024-06-07 13-39-45 Screenshot from 2024-06-07 13-38-26

Fixes #78746
Fixes #92851
Part of #101620
Fixes #147483
Fixes #153274
Part of Google b/317115348
Fixes optionsViewOpenDirection not working, mentioned in #143249 (comment).

Requirements

  • By default, the width of the options matches the width of the field.
  • Options can be aligned to the start or end for LTR/RTL languages.
  • The optionsViewOpenDirection parameter is respected.
  • If the options would vertically exceed the top or bottom of the screen, they reposition themselves to fit on the screen while covering the field. At least enough to tap an option. This has accessibility implications, because sometimes an Autocomplete near the edge of a narrow screen can be unusable.
  • If the Autocomplete is in a ScrollView, then the options move along with the field during scrolling.
  • When the field moves or resizes, the options position and size change to match. Even if the field is animated.
  • The options layout updates on the same frame that the field layout changes.

It's probably not possible to check all of these boxes so we'll probably need to compromise.

Tools that I've used to try to achieve this

  • LayoutBuilder to provide the field constraints1.
  • Looking up layout information of a widget via GlobalKey.
  • CompositedTransformFollower/Target.
  • CustomSingleChildLayout.

Footnotes

  1. Originally this didn't work due to a bug when using LayoutBuilder with OverlayPortal (https://github.com/flutter/flutter/pull/147856). That has now been fixed.

@justinmc justinmc self-assigned this Feb 9, 2024
@github-actions github-actions bot added the framework flutter/packages/flutter repository. See also f: labels. label Feb 9, 2024
@justinmc justinmc force-pushed the autocomplete-options-width branch 3 times, most recently from 795e79b to d93bb04 Compare February 9, 2024 21:37
@justinmc
Copy link
Contributor Author

justinmc commented Feb 9, 2024

@LongCatIsLooong Would something like this work in OverlayPortal? Or maybe better, _optionsLayerLink.leaderSize.width?

builder: (BuildContext context) => widget.optionsViewBuilder(context, _select, _options),
builder: (BuildContext context) {
final Widget options = widget.optionsViewBuilder(context, _select, _options);
final RenderBox? fieldRenderBox = _fieldKey.currentContext?.findRenderObject() as RenderBox?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it guaranteed at this point that the render object is already laid out?

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 was hoping you would know :) This method is the overlayChildBuilder of an OverlayPortal, and _fieldKey is in the child of the same OverlayPortal.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the sizing rule (BoxConstraints => Size) is known (in other words the child is sized by the parent) I think you could use a layout builder as the ancestor of the text field to get the size of the text field. Otherwise I think it's a bit trickier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done and it seems to work, thanks.

@justinmc justinmc force-pushed the autocomplete-options-width branch from 0e94b95 to e4bce30 Compare February 12, 2024 18:37
@justinmc justinmc marked this pull request as ready for review February 13, 2024 00:11
Comment on lines 448 to 449
width: _fieldBoxConstraints.maxWidth,
child: widget.optionsViewBuilder(context, _select, _options),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Question: Should I add a flag for this, or even pass the Size to the optionsViewBuilder and make users include the UnconstrainedBox and SizedBox? I figure most users do want this width-matching behavior, which is why I made it mandatory, but it could be a breaking change for some...

Copy link
Member

Choose a reason for hiding this comment

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

I agree that most users will want this width-matching behavior — I think it's the standard expectation which most end users are accustomed to from other GUIs.

A flag would be reasonable, because I do sometimes see UIs where there's a small dropdown field but when you open it the options are much wider, so there'll surely at some point be a Flutter developer who wants that behavior. I think it'd be best for width-matching to be the default, though, because that's the behavior most people will expect. My suggestion would be to go ahead and make it the only behavior, and then adding a flag can be a follow-up if and when people ask for it.

If users had to include the UnconstrainedBox and SizedBox themselves, I think that'd make this only a like 20% solution, because that'd sound complicated and I expect most developers just wouldn't do it. (With enough docs, it could be maybe a 70% solution; even then, I'd expect to continue getting reports of #78746.) It definitely feels to me like the kind of detail I'd expect the autocomplete widget itself to have responsibility for.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the analysis, I'm on board with this. I'll leave this as-is but add a TODO with a link to this thread if/when someone starts asking for it.

final OverlayPortalController _optionsViewController = OverlayPortalController(debugLabel: '_RawAutocompleteState');

// The box constraints that the field was last built with.
late BoxConstraints _fieldBoxConstraints;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm surprised that no test caught this, but I think this will crash if the autocomplete widget is not in a layout builder. The _fieldBoxConstraints variable won't be assigned until the layout phase.

As a workaround, putting the widget that takes that BoxConstraints in a layout builder will defer the evaluation of the late variable to the layout phase.

Overlay portal always lays out the child branch before the overlayChild branch so I think this should be safe (but I think the trick makes enough assumptions about layout builder's implementation and overlayportal's implementation, to merit throughout comments/testing)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's my attempt: 49f98d6. Can you take a look at that and tell me if it's possible to get OverlayPortal to build overlayChildBuilder in the same frame as it builds child? I wasn't able to reproduce it, though I get what you're saying and it is worrying.

Copy link
Contributor

Choose a reason for hiding this comment

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

Try letting it build a field view that has a Focus(autofocus: true)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah that probably doesn't work since the focus callback won't get called until the next microtask flush. If it's guaranteed that the options view won't show the same frame the field view shows, that should be safe (but _fieldBoxConstraints will still have a one frame delay).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok got it, I've removed the LayoutBuilder, the test, and adjusted my comments. I will keep an eye out though in case anyone runs into this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the layout builder is probably still needed. If the text field gets a set of different input constraints, then this variable is going to change, but nothing is telling the menu to resize if it's already open?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well I guess OverlayPortal needs a new constructor that takes a overlayChildLayoutBuilder which gives you the size of the child before you build the overlay child.

Copy link
Contributor

Choose a reason for hiding this comment

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

(Because the text field itself may change size, e.g., when there's an animation that resizes it)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I confirmed that the options weren't rebuilding with the new width when the field constraints changed. Now I've updated it with a ValueNotifier so that they do, but I agree a new constructor on OverlayPortal would be nice. What do you think of my approach, and do you think I should add something similar to OverlayPortal or keep that for a separate PR?

Screencast.from.2024-02-21.12-57-46.webm

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Neat! Glad to see this.

Comment on lines 448 to 449
width: _fieldBoxConstraints.maxWidth,
child: widget.optionsViewBuilder(context, _select, _options),
Copy link
Member

Choose a reason for hiding this comment

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

I agree that most users will want this width-matching behavior — I think it's the standard expectation which most end users are accustomed to from other GUIs.

A flag would be reasonable, because I do sometimes see UIs where there's a small dropdown field but when you open it the options are much wider, so there'll surely at some point be a Flutter developer who wants that behavior. I think it'd be best for width-matching to be the default, though, because that's the behavior most people will expect. My suggestion would be to go ahead and make it the only behavior, and then adding a flag can be a follow-up if and when people ask for it.

If users had to include the UnconstrainedBox and SizedBox themselves, I think that'd make this only a like 20% solution, because that'd sound complicated and I expect most developers just wouldn't do it. (With enough docs, it could be maybe a 70% solution; even then, I'd expect to continue getting reports of #78746.) It definitely feels to me like the kind of detail I'd expect the autocomplete widget itself to have responsibility for.

final OverlayPortalController _optionsViewController = OverlayPortalController(debugLabel: '_RawAutocompleteState');

// The box constraints that the field was last built with.
late BoxConstraints _fieldBoxConstraints;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the layout builder is probably still needed. If the text field gets a set of different input constraints, then this variable is going to change, but nothing is telling the menu to resize if it's already open?

final OverlayPortalController _optionsViewController = OverlayPortalController(debugLabel: '_RawAutocompleteState');

// The box constraints that the field was last built with.
late BoxConstraints _fieldBoxConstraints;
Copy link
Contributor

Choose a reason for hiding this comment

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

Well I guess OverlayPortal needs a new constructor that takes a overlayChildLayoutBuilder which gives you the size of the child before you build the overlay child.

highlightIndexNotifier: _highlightedOptionIndex,
child: Builder(
builder: (BuildContext context) => widget.optionsViewBuilder(context, _select, _options),
child: UnconstrainedBox(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the UnconstrainedBox? The overlay should be large enough to accommodate the menu I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without that the width of the SizedBox is not respected and the options are always full width. I think being in an Overlay gives it weird constraints.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use Positioned instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That doesn't seem to work because of the CompositedTransformFollower widget. Without that Positioned does work like you said.

Copy link
Contributor

Choose a reason for hiding this comment

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

Positioned has to be placed as the root of the overlay child. I think the problem is the overlay is giving the overlay child tight constraints.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah that worked, thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, with Positioned it was completely unconstrained in both directions, which caused layout errors in the options builder. I changed it to use ConstraintsTransformBox to just loosen the constraints in both directions, what do you think of that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Talked offline: I should try setting the width at the Positioned widget instead of below in a SizedBox.

highlightIndexNotifier: _highlightedOptionIndex,
child: Builder(
builder: (BuildContext context) => widget.optionsViewBuilder(context, _select, _options),
child: UnconstrainedBox(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use Positioned instead?

?? (_internalTextEditingController = TextEditingController.fromValue(widget.initialValue));
initialController.addListener(_onChangedField);
widget.focusNode?.addListener(_updateOptionsViewVisibility);
if (widget.focusNode?.hasFocus ?? false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this still 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.

No, good catch.

// overlayChildBuilder.
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
_fieldBoxConstraints.value = boxConstraints;
Copy link
Contributor

Choose a reason for hiding this comment

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

If the input field has textWidthBasis = TextWidthBasis.longestLine (in which case the text field is not going to take the full available width) does this work still?

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're right, and this comment is making me realize that what we're doing here isn't matching the width of the field necessarily, it's just matching the width of the constraints given to the field. Here's a screenshot of your idea where yellow is the Autocomplete and blue is the EditableText:

Screenshot from 2024-02-28 15-59-18

It doesn't even need to be anything as fancy as TextWidthBasis; if you just hardcode the width of the field to something other than the constraints, then it's the same kind of result:

Screenshot from 2024-02-28 16-11-46

However, I think that's kind of the best that we can do. If users are sizing their text field in a weird way and they want the options to match that width, they'll need to look up its width using a GlobalKey and a post frame callback. Most people are probably doing that today without this PR.

My best guess is that giving the same constraints as the Autocomplete is probably what most users will want and expect.

child: CompositedTransformTarget(
link: _optionsLayerLink,
child: fieldView,
// This LayoutBuilder ensures that _fieldBoxConstraints is assigned
Copy link
Contributor

Choose a reason for hiding this comment

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

If the options view is also in a layout builder I don't think the post-frame callback is 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.

I get the "setState called during build" error if I remove it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think in that case the setState isn't necessary either. If the overlay portal and its child get a different BoxConstraints, the overlay child will relayout too. And the layout builder will rebuild. I think that happens in the same frame so it would work better than using a post frame callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm it doesn't seem to work when I remove that. When the constraints change, the LayoutBuilder in OverlayPortal.child is called with the new constraints, but the overlayChildBuilder is not called.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was wrong about using a layout builder. If child gets a different set of constraints, the layout builder callback will be called with the new constraints. But overlay child is laid out using the Overlay's constraints so its layout builder won't be called.


return Positioned(
width: widget.fieldBoxConstraints.value.maxWidth,
// TODO(justinmc): If I don't give a height, height constraints are
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Mar 4, 2024

Choose a reason for hiding this comment

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

IIRC the Overlay does not give its children unbounded constraints (it should at least be bound by the physical size of the screen). Do you mean a tight height constraint or a loose height constraint?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Loose: 0.0<=h<=Infinity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without the Positioned widget the constraints are tight to the size of the screen.

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Mar 5, 2024

Choose a reason for hiding this comment

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

Ahhh I see, for positioned children it's not bounded. But why didn't the options view child no like 0.0<=h<=Infinity? Because it's using Align so it doesn't know how to vertically align with infinite amount of vertical space? In that case I guess we have to set Positioned widget's top and bottom to 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, that seems to work!

The test that failed has a ListView in the optionsViewBuilder, which I think was the problem.

@justinmc justinmc requested a review from LongCatIsLooong March 5, 2024 21:43
controller: _optionsViewController,
overlayChildBuilder: _buildOptionsView,
overlayChildBuilder: (BuildContext context) {
return _OptionsView<T>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Use a ValueListenableBuilder instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

also consider adding an assert to verify the constraints is not unbounded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I forgot ValueListenableBuilder was a thing. Much cleaner.

@justinmc
Copy link
Contributor Author

justinmc commented Mar 12, 2024

I've tracked down the bug from the failing Google tests. Disposing the field's TextEditingController at the time that an option is selected results in:

The following assertion was thrown building RawGestureDetector(state:
RawGestureDetectorState#6436d(gestures: [tap, long press, tap and pan], excludeFromSemantics: true,
behavior: translucent)):
A TextEditingController was used after being disposed.
Once you have called dispose() on a TextEditingController, it can no longer be used.

The bug happens like this:

  1. An option is tapped, and the TextEditingController is modified with the new selected value.
  2. TextFormField calls setState due to the changed controller.
  3. The controller is disposed.
  4. TextFormField builds with the disposed controller.

The cause is the LayoutBuilder added in this PR. It delays the build until after the controller has been disposed but before it's been replaced.

I think the question is: Is this something that should work, or should we discourage messing with the controller like this? @LongCatIsLooong

Full error
The following assertion was thrown building RawGestureDetector(state:
RawGestureDetectorState#6436d(gestures: [tap, long press, tap and pan], excludeFromSemantics: true,
behavior: translucent)):
A TextEditingController was used after being disposed.
Once you have called dispose() on a TextEditingController, it can no longer be used.

The relevant error-causing widget was:
  TextFormField
  TextFormField:file:///usr/local/google/home/jmccandless/Projects/issues/sandbox/lib/main.dart:57:16

When the exception was thrown, this was the stack:
#0      ChangeNotifier.debugAssertNotDisposed.<anonymous closure> (package:flutter/src/foundation/change_notifier.dart:179:9)
#1      ChangeNotifier.debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:186:6)
#2      ChangeNotifier.addListener (package:flutter/src/foundation/change_notifier.dart:271:27)
#3      _MergingListenable.addListener (package:flutter/src/foundation/change_notifier.dart:499:14)
#4      _AnimatedState.didUpdateWidget (package:flutter/src/widgets/transitions.dart:118:25)
#5      StatefulElement.update (package:flutter/src/widgets/framework.dart:5659:55)
#6      Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#7      SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#8      Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#9      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#10     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#11     Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#12     StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#13     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#14     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#15     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#16     Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#17     StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#18     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#19     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#20     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#21     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#22     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#23     Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#24     StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#25     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#26     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#27     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#28     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#29     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#30     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#31     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#32     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#33     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#34     Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#35     StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#36     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#37     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#38     Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#39     ProxyElement.update (package:flutter/src/widgets/framework.dart:5816:5)
#40     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#41     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#42     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#43     Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#44     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2905:19)
#45     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1021:21)
#46     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:443:5)
#47     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1388:15)
#48     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1313:9)
#49     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:5)
#50     _invoke (dart:ui/hooks.dart:312:13)
#51     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5)
#52     _drawFrame (dart:ui/hooks.dart:283:31)

════════════════════════════════════════════════════════════════════════════════════════════════════
Repro code
import 'package:flutter/material.dart';

void main() => runApp(const AutocompleteExampleApp());

class AutocompleteExampleApp extends StatelessWidget {
  const AutocompleteExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Autocomplete Basic'),
        ),
        body: const Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Padding(
                padding: EdgeInsets.symmetric(horizontal: 32.0),
                child: _MyAutocomplete(),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _MyAutocomplete extends StatefulWidget {
  const _MyAutocomplete();

  @override
  State<_MyAutocomplete> createState() => _MyAutocompleteState();
}

class _MyAutocompleteState extends State<_MyAutocomplete> {
  final FocusNode focusNode = FocusNode();
  TextEditingController controller = TextEditingController(text: 'First text');

  static const List<String> _kOptions = <String>[
    'aardvark',
    'bobcat',
    'chameleon',
  ];

  @override
  Widget build(BuildContext context) {
    return RawAutocomplete<String>(
      textEditingController: controller,
      focusNode: focusNode,
      fieldViewBuilder: (BuildContext buildContext, TextEditingController controller, FocusNode focusNode, VoidCallback onSubmitted) {
        debugPrint('fieldviewbuilder ${controller.text}');
        // TODO(justinmc): This must be TFF to reproduce, not TF, because TFF
        // listens to the controller and rebuilds if it changes.
        return TextFormField(
          controller: controller,
          focusNode: focusNode,
          onFieldSubmitted: (String value) => onSubmitted(),
        );
      },
      optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
        return Column(
          children: <Widget>[
            ...options.map((String option) {
              return TextButton(
                onPressed: () {
                  onSelected(option);
                },
                child: Text(option),
              );
            }),
          ],
        );
      },
      optionsBuilder: (TextEditingValue textEditingValue) {
        return _kOptions;
      },
      onSelected: (String selection) {
        debugPrint('You just selected $selection');
        setState(() {
          controller.dispose();
          controller = TextEditingController(text: 'Last text');
        });
      },
    );
  }
}

@LongCatIsLooong
Copy link
Contributor

LongCatIsLooong commented Mar 12, 2024

Ah interesting. onSelected only gets called when the user taps the selection right?

@justinmc
Copy link
Contributor Author

@LongCatIsLooong Yes (though I could use the keyboard or something if I wanted to). Also, the bug doesn't happen if I do the whole controller swap based on a separate button click or something, only when it happens as a part of onSelected. That's because the RawAutocomplete code updates the TextEditingController before calling onSelected.

@LongCatIsLooong
Copy link
Contributor

LongCatIsLooong commented Mar 13, 2024

  1. The controller is disposed.
  2. TextFormField builds with the disposed controller.

So the onSelect callback is called between, say, frame n and n + 1, and the controller is disposed, then when we build the widget tree for frame n + 1:

_MyAutocompleteState should rebuild first, which rebuilds RawAutocomplete, and RawAutocomplete's state should be using the new controller now. What am I missing?

@wmadden
Copy link

wmadden commented Apr 2, 2024

Any progress here? The Autocomplete widget isn't really usable as it is

@wmadden
Copy link

wmadden commented Apr 4, 2024

@justinmc you can solve this problem much more simply, without using the LayoutBuilder.

The issue is actually the OverlayEntry, which will size its child to occupy as much space as possible. You can use the existing fieldKey to get the RenderBox of the text field and use that to size the options list widget. You'll need to introduce a container between the OverlayEntry and the options list to change the sizing constraints.

That way you don't need the postframe callback, which should simplify the tests

@wmadden
Copy link

wmadden commented Apr 4, 2024

Incidentally, and I suppose this should be a separate bug ticket, the Autocomplete optionsViewOpenDirection: OptionsViewOpenDirection.up doesn't function at all because the options list is positioned way off the screen. Because of the same issue with the OverlayEntry.

You can reproduce using the live example on the Autocomplete docs page, just set optionsViewOpenDirection to up: https://api.flutter.dev/flutter/material/Autocomplete-class.html

@justinmc
Copy link
Contributor Author

justinmc commented Apr 6, 2024

@wmadden You're right about the OptionsViewOpenDirection bug, can you open a separate issue? I'll investigate if this fix is related to whatever we should do to fix that bug too.

About your proposed solution, I believe it won't update the options when the size of the field changes. Also, using a GlobalKey to get the size of another widget requires that you wait until that widget has been laid out, so I think they would still be 1 frame delayed from each other (maybe not a terrible problem).

@auto-submit
Copy link
Contributor

auto-submit bot commented Jan 15, 2025

Time to revert pull request flutter/flutter/143249 has elapsed.
You need to open the revert manually and process as a regular pull request.

@auto-submit auto-submit bot removed the revert Autorevert PR (with "Reason for revert:" comment) label Jan 15, 2025
jacobsimionato added a commit that referenced this pull request Jan 15, 2025
maheshj01 pushed a commit to maheshj01/flutter that referenced this pull request Jan 15, 2025
| Problem | Before | After |
| --- | --- | --- |
| Width | <img width="797" alt="Screenshot 2024-02-09 at 1 29 09 PM"
src="https://github.com/flutter/flutter/assets/389558/c49fa584-2550-41f6-ab80-6c20d01412b1">
| <img width="794" alt="Screenshot 2024-02-09 at 1 23 59 PM"
src="https://github.com/flutter/flutter/assets/389558/1326f797-9883-4916-9de3-1939e7648d46">
|
| Overflow | ![Screenshot from 2024-06-07
13-39-45](https://github.com/flutter/flutter/assets/389558/8a24c87a-2b5e-4bdc-8347-339d850f5a82)
| ![Screenshot from 2024-06-07
13-38-26](https://github.com/flutter/flutter/assets/389558/735248aa-8969-413b-a6cf-4f9b708f9ea8)
|

Fixes flutter#78746
Fixes flutter#92851
Part of flutter#101620
Fixes flutter#147483
Fixes flutter#153274
Part of Google b/317115348
Fixes optionsViewOpenDirection not working, mentioned in
flutter#143249 (comment).

### Requirements

* [x] By default, the width of the options matches the width of the
field.
 * [x] Options can be aligned to the start or end for LTR/RTL languages.
 * [x] The optionsViewOpenDirection parameter is respected.
* [x] If the options would vertically exceed the top or bottom of the
screen, they reposition themselves to fit on the screen while covering
the field. At least enough to tap an option. This has accessibility
implications, because sometimes an Autocomplete near the edge of a
narrow screen can be unusable.
* [x] If the Autocomplete is in a ScrollView, then the options move
along with the field during scrolling.
* [x] When the field moves or resizes, the options position and size
change to match. Even if the field is animated.
* [ ] The options layout updates on the same frame that the field layout
changes.

It's probably not possible to check all of these boxes so we'll probably
need to compromise.

 #### Tools that I've used to try to achieve this

 * LayoutBuilder to provide the field constraints[^1].
 * Looking up layout information of a widget via GlobalKey.
 * CompositedTransformFollower/Target.
 * CustomSingleChildLayout.

[^1]: Originally this didn't work due to a bug when using LayoutBuilder
with OverlayPortal (flutter#147856).
That has now been fixed.

Co-authored-by: Victor Sanni <[email protected]>
gnprice pushed a commit to victorsanni/zulip-flutter that referenced this pull request Jan 15, 2025
…mplete

The fixes to RawAutocomplete's options width in the Flutter framework:
  flutter/flutter#143249

introduces a one-frame delay, so this test will need an extra pump to
account for that.
gnprice added a commit to gnprice/flutter_customer_testing that referenced this pull request Jan 15, 2025
This has the update to unblock autocomplete changes:
  zulip/zulip-flutter#1190
  flutter/flutter#143249

and with that, tests pass again on Flutter main.
github-merge-queue bot pushed a commit that referenced this pull request Jan 15, 2025
Reverts #143249

This broke some Google tests sadly - see b/390128156
ahmedrasar pushed a commit to ahmedrasar/flutter that referenced this pull request Jan 16, 2025
Reverts flutter#143249

This broke some Google tests sadly - see b/390128156
ahmedrasar pushed a commit to ahmedrasar/flutter that referenced this pull request Jan 16, 2025
Reverts flutter#143249

This broke some Google tests sadly - see b/390128156
ahmedrasar pushed a commit to ahmedrasar/flutter that referenced this pull request Jan 16, 2025
Reverts flutter#143249

This broke some Google tests sadly - see b/390128156
github-merge-queue bot pushed a commit that referenced this pull request Jan 17, 2025
Original PR: #143249
Revert PR: #161666

## Pre-launch Checklist

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

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

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Gaurav-Kushwaha-1225 pushed a commit to Gaurav-Kushwaha-1225/zulip-flutter that referenced this pull request Jan 21, 2025
…mplete

The fixes to RawAutocomplete's options width in the Flutter framework:
  flutter/flutter#143249

introduces a one-frame delay, so this test will need an extra pump to
account for that.
auto-submit bot pushed a commit to flutter/tests that referenced this pull request Jan 21, 2025
This has the update to unblock autocomplete changes:
  zulip/zulip-flutter#1190
  flutter/flutter#143249

and with that, tests pass again on Flutter main.

Also install libsqlite3-dev on Linux, a package which is no longer
included in GitHub's new version of the "ubuntu-latest" images.  The
lack of it causes nondeterministic failures when the runner happens
to use the new image:
  #441 (comment)

The actual shared library file libsqlite3.so.0.8.6 -- in the package
libsqlite3-0 -- is still installed by default anyway.  This package
adds the symlink at "libsqlite3.so", which is the filename some
libraries apparently look for.
auto-submit bot added a commit to flutter/tests that referenced this pull request Jan 22, 2025
Reverts: #441
Initiated by: matanlurey
Reason for reverting: sudo apt-get install does not work on LUCI
Original PR Author: gnprice

Reviewed By: {Piinks}

This change reverts the following previous change:
This has the update to unblock autocomplete changes:
  zulip/zulip-flutter#1190
  flutter/flutter#143249

and with that, tests pass again on Flutter main.

Also install libsqlite3-dev on Linux, a package which is no longer
included in GitHub's new version of the "ubuntu-latest" images.  The
lack of it causes nondeterministic failures when the runner happens
to use the new image:
  #441 (comment)

The actual shared library file libsqlite3.so.0.8.6 -- in the package
libsqlite3-0 -- is still installed by default anyway.  This package
adds the symlink at "libsqlite3.so", which is the filename some
libraries apparently look for.
victoreronmosele pushed a commit to victoreronmosele/tests that referenced this pull request Feb 5, 2025
This has the update to unblock autocomplete changes:
  zulip/zulip-flutter#1190
  flutter/flutter#143249

and with that, tests pass again on Flutter main.

Also install libsqlite3-dev on Linux, a package which is no longer
included in GitHub's new version of the "ubuntu-latest" images.  The
lack of it causes nondeterministic failures when the runner happens
to use the new image:
  flutter#441 (comment)

The actual shared library file libsqlite3.so.0.8.6 -- in the package
libsqlite3-0 -- is still installed by default anyway.  This package
adds the symlink at "libsqlite3.so", which is the filename some
libraries apparently look for.
victoreronmosele pushed a commit to victoreronmosele/tests that referenced this pull request Feb 5, 2025
Reverts: flutter#441
Initiated by: matanlurey
Reason for reverting: sudo apt-get install does not work on LUCI
Original PR Author: gnprice

Reviewed By: {Piinks}

This change reverts the following previous change:
This has the update to unblock autocomplete changes:
  zulip/zulip-flutter#1190
  flutter/flutter#143249

and with that, tests pass again on Flutter main.

Also install libsqlite3-dev on Linux, a package which is no longer
included in GitHub's new version of the "ubuntu-latest" images.  The
lack of it causes nondeterministic failures when the runner happens
to use the new image:
  flutter#441 (comment)

The actual shared library file libsqlite3.so.0.8.6 -- in the package
libsqlite3-0 -- is still installed by default anyway.  This package
adds the symlink at "libsqlite3.so", which is the filename some
libraries apparently look for.
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 12, 2025
github-actions bot pushed a commit to zulip/zulip-flutter that referenced this pull request Feb 12, 2025
…mplete

The fixes to RawAutocomplete's options width in the Flutter framework:
  flutter/flutter#143249

introduces a one-frame delay, so this test will need an extra pump to
account for that.
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 6, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 7, 2025
androidseb pushed a commit to androidseb/packages that referenced this pull request Jun 8, 2025
Roll Flutter from 72db8f6 to 40c2b86 (33 revisions)

flutter/flutter@72db8f6...40c2b86

2025-01-14 [email protected] update changelog for 3.27.2 release (flutter/flutter#161569)
2025-01-14 [email protected] Fix `showLicensePage` does not inherit ambient `Theme` (flutter/flutter#161599)
2025-01-14 [email protected] Added special case for fat width arcs (flutter/flutter#161255)
2025-01-14 [email protected] Replace `fetch `with `gclient sync`. (flutter/flutter#161565)
2025-01-14 [email protected] Roll Packages from 3c3bc68 to d1fd623 (4 revisions) (flutter/flutter#161597)
2025-01-14 [email protected] Remove unused method (flutter/flutter#161572)
2025-01-14 [email protected] Fix crash when closing a window with `Alt+F4` in multi-win Flutter on Windows (flutter/flutter#161375)
2025-01-14 [email protected] Update InputDecoration.border documentation (flutter/flutter#161415)
2025-01-14 [email protected] [Web] Allow specifying the strategy on when to use <img> element to display images (flutter/flutter#159917)
2025-01-14 [email protected] Roll Dart to  Version 3.7.0-323.0.dev (flutter/flutter#161567)
2025-01-14 [email protected] Use wildcards (flutter/flutter#161548)
2025-01-14 [email protected] Autocomplete Options Width (flutter/flutter#143249)
2025-01-13 [email protected] Move the analyzer_benchmark to Mac arm64 devicelab bots (flutter/flutter#161405)
2025-01-13 [email protected] Remove references to `cirrus`, mostly in doc comments. (flutter/flutter#161529)
2025-01-13 [email protected] Fix paths when running clang-tidy on git diffs (flutter/flutter#161496)
2025-01-13 [email protected] [web:a11y] treat empty tappables as buttons (flutter/flutter#161360)
2025-01-13 [email protected] Add route settings to CupertinoSheetRoute (flutter/flutter#161528)
2025-01-13 [email protected] Copy `linux_host_engine` as `linux_host_engine_test`, removing `archives: [...]`. (flutter/flutter#161532)
2025-01-13 [email protected] Remove last two references to Cirrus CI. (flutter/flutter#161530)
2025-01-13 [email protected] Mark `Mac_mokey microbenchmarks` as flakey (flutter/flutter#161550)
2025-01-13 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Match CupertinoPageTransitionsBuilder animation duration to CupertinoPageRoute (#160241)" (flutter/flutter#161555)
2025-01-13 [email protected] Add validator execution times to `flutter doctor --verbose` (flutter/flutter#158124)
2025-01-13 [email protected] Explain more specifically how to use `flutter drive`/what it does (flutter/flutter#161450)
2025-01-13 [email protected] Fixed repeated strings for incompatible Gradle or AGP version in `create` command (flutter/flutter#161223)
2025-01-13 [email protected] Remove `WEB_SHARD_COUNT`, which no longer exists post-Cirrus. (flutter/flutter#161527)
2025-01-13 [email protected] [Impeller] Update guidance on prebuilt artifacts. (flutter/flutter#161251)
2025-01-13 [email protected] Migrate DisplayList unit tests to DL/Impeller geometry classes (flutter/flutter#161453)
2025-01-13 [email protected] Context menu button callback docs clarification (flutter/flutter#161451)
2025-01-13 [email protected] Match CupertinoPageTransitionsBuilder animation duration to CupertinoPageRoute (flutter/flutter#160241)
2025-01-13 [email protected] Udpate documentation on the third_party directories (flutter/flutter#161407)
2025-01-13 [email protected] Propagate environment variables when `flutter drive` is invoked. (flutter/flutter#161452)
2025-01-13 [email protected] Convert base application name handling to kotlin source (start of FGP kt conversion) (flutter/flutter#155963)
2025-01-13 [email protected] [Impeller] remove API 30 restriction for SurfaceControl testing. (flutter/flutter#161438)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
...
FMorschel pushed a commit to FMorschel/packages that referenced this pull request Jun 9, 2025
Roll Flutter from 72db8f6 to 40c2b86 (33 revisions)

flutter/flutter@72db8f6...40c2b86

2025-01-14 [email protected] update changelog for 3.27.2 release (flutter/flutter#161569)
2025-01-14 [email protected] Fix `showLicensePage` does not inherit ambient `Theme` (flutter/flutter#161599)
2025-01-14 [email protected] Added special case for fat width arcs (flutter/flutter#161255)
2025-01-14 [email protected] Replace `fetch `with `gclient sync`. (flutter/flutter#161565)
2025-01-14 [email protected] Roll Packages from 3c3bc68 to d1fd623 (4 revisions) (flutter/flutter#161597)
2025-01-14 [email protected] Remove unused method (flutter/flutter#161572)
2025-01-14 [email protected] Fix crash when closing a window with `Alt+F4` in multi-win Flutter on Windows (flutter/flutter#161375)
2025-01-14 [email protected] Update InputDecoration.border documentation (flutter/flutter#161415)
2025-01-14 [email protected] [Web] Allow specifying the strategy on when to use <img> element to display images (flutter/flutter#159917)
2025-01-14 [email protected] Roll Dart to  Version 3.7.0-323.0.dev (flutter/flutter#161567)
2025-01-14 [email protected] Use wildcards (flutter/flutter#161548)
2025-01-14 [email protected] Autocomplete Options Width (flutter/flutter#143249)
2025-01-13 [email protected] Move the analyzer_benchmark to Mac arm64 devicelab bots (flutter/flutter#161405)
2025-01-13 [email protected] Remove references to `cirrus`, mostly in doc comments. (flutter/flutter#161529)
2025-01-13 [email protected] Fix paths when running clang-tidy on git diffs (flutter/flutter#161496)
2025-01-13 [email protected] [web:a11y] treat empty tappables as buttons (flutter/flutter#161360)
2025-01-13 [email protected] Add route settings to CupertinoSheetRoute (flutter/flutter#161528)
2025-01-13 [email protected] Copy `linux_host_engine` as `linux_host_engine_test`, removing `archives: [...]`. (flutter/flutter#161532)
2025-01-13 [email protected] Remove last two references to Cirrus CI. (flutter/flutter#161530)
2025-01-13 [email protected] Mark `Mac_mokey microbenchmarks` as flakey (flutter/flutter#161550)
2025-01-13 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Match CupertinoPageTransitionsBuilder animation duration to CupertinoPageRoute (#160241)" (flutter/flutter#161555)
2025-01-13 [email protected] Add validator execution times to `flutter doctor --verbose` (flutter/flutter#158124)
2025-01-13 [email protected] Explain more specifically how to use `flutter drive`/what it does (flutter/flutter#161450)
2025-01-13 [email protected] Fixed repeated strings for incompatible Gradle or AGP version in `create` command (flutter/flutter#161223)
2025-01-13 [email protected] Remove `WEB_SHARD_COUNT`, which no longer exists post-Cirrus. (flutter/flutter#161527)
2025-01-13 [email protected] [Impeller] Update guidance on prebuilt artifacts. (flutter/flutter#161251)
2025-01-13 [email protected] Migrate DisplayList unit tests to DL/Impeller geometry classes (flutter/flutter#161453)
2025-01-13 [email protected] Context menu button callback docs clarification (flutter/flutter#161451)
2025-01-13 [email protected] Match CupertinoPageTransitionsBuilder animation duration to CupertinoPageRoute (flutter/flutter#160241)
2025-01-13 [email protected] Udpate documentation on the third_party directories (flutter/flutter#161407)
2025-01-13 [email protected] Propagate environment variables when `flutter drive` is invoked. (flutter/flutter#161452)
2025-01-13 [email protected] Convert base application name handling to kotlin source (start of FGP kt conversion) (flutter/flutter#155963)
2025-01-13 [email protected] [Impeller] remove API 30 restriction for SurfaceControl testing. (flutter/flutter#161438)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. will affect goldens Changes to golden files

Projects

None yet

9 participants