Ibrahim Ali
Ibrahim Ali

Reputation: 2511

Flutter 2.0 - Autocomplete widget goes out of screen from right side

How do I make the autocomplete box be the same size with the TextField which doesn't have a specific width, it takes up the maximum width.

              Autocomplete(
                optionsBuilder: (TextEditingValue textEditingValue) {
                  if (textEditingValue.text == '') {
                    return ['aa', 'bb', 'cc', 'aa', 'bb', 'cc'];
                  }
                  return ['aa', 'bb', 'cc', 'aa', 'bb', 'cc']
                      .where((String option) {
                    return option
                        .toString()
                        .contains(textEditingValue.text.toLowerCase());
                  });
                },
                onSelected: (option) {
                  print(option);
                },
              ),

enter image description here

Upvotes: 42

Views: 21323

Answers (10)

LinhNV
LinhNV

Reputation: 29

I just do like default builder source, and it just work property. I think it's error happened because not has parent config.

optionsViewBuilder: (context, onSelected, options) {
    return Align(
      alignment: Alignment.topLeft,
      child: Material(
        elevation: 4.0,
        child: ConstrainedBox(
          constraints: BoxConstraints(maxHeight: 100.r),
          child: Container(
            width: 100.r,
            height: 100.r,
            decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(12.r),
                border: Border.all(
                  color: context.oldTheme.lightGrey,
                )),
            child: ListView.builder(
              itemBuilder: (context, index) {
                return Text(
                  options.toList()[index],
                  style: context.oldTheme.textTheme.body2,
                );
              },
              itemCount: options.length,
            ),
          ),
        ),
      ),
    );
  },

Upvotes: 0

Sure
Sure

Reputation: 113

In my case, I just had to wrap the widget that I'm returning in optionsBuilder in an Align. After doing that, it started working as intended, creating the menu under the input field without stretching out of the screen. I have also used LayoutBuilder's constraints.maxWidth to define the menu's width.

Example:

LayoutBuilder(
  builder: (context, constraints) {
    return RawAutocomplete<String>(
      optionsViewBuilder: (context, onSelected, options) {
        return Align(
          alignment: Alignment.topLeft,
          child: SizedBox(
            width: constraints.maxWidth,
            child: Material(
              elevation: 4,
              clipBehavior: Clip.antiAlias,
              shape: const RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(10)),
              ),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: options.map((e) {
                  return ListTile(title: Text(e));
                }).toList(),
              ),
            ),
          ),
        );
      },
    );
  },
);

enter image description here

Upvotes: 7

pddthinh
pddthinh

Reputation: 161

Still happen in flutter 3.3.5 ...

My workaround is wrapping the Autocomplete inside a LayoutBuilder and using the constraints.maxWidth to set the width for the optionsViewBuilder's ListView.

The detailed snippet is here: https://github.com/flutter/flutter/issues/78746#issuecomment-1326488333

Upvotes: 3

user10065900
user10065900

Reputation:

I have seen source code autocomplete.dart, there is missing parameter width, but height exists, I have added width by hand, and it worked.

class Autocomplete<T extends Object> extends StatelessWidget {
/// Creates an instance of [Autocomplete].
const Autocomplete({
Key? key,
required this.optionsBuilder,
this.displayStringForOption = RawAutocomplete.defaultStringForOption,
this.fieldViewBuilder = _defaultFieldViewBuilder,
this.onSelected,
this.optionsMaxHeight = 200.0,
this.optionsMaxWidth = 285.0 // I added this line
this.optionsViewBuilder,
this.initialValue,

/// The default value is set to 200.
final double optionsMaxHeight;
final double optionsMaxWidth; // Added this line

 @override
Widget build(BuildContext context) {
return RawAutocomplete<T>(
  displayStringForOption: displayStringForOption,
  fieldViewBuilder: fieldViewBuilder,
  initialValue: initialValue,
  optionsBuilder: optionsBuilder,
  optionsViewBuilder: optionsViewBuilder ?? (BuildContext context, AutocompleteOnSelected<T> onSelected, Iterable<T> options) {
    return _AutocompleteOptions<T>(
      displayStringForOption: displayStringForOption,
      onSelected: onSelected,
      options: options,
      maxOptionsHeight: optionsMaxHeight,
      maxOptionsWidth: optionsMaxWidth, // Added this line
    );
  },
  onSelected: onSelected,
);
}


// The default Material-style Autocomplete options.
 class _AutocompleteOptions<T extends Object> extends StatelessWidget {
  const _AutocompleteOptions({
  Key? key,
  required this.displayStringForOption,
  required this.onSelected,
  required this.options,
required this.maxOptionsHeight,
required this.maxOptionsWidth // Added this line
}) : super(key: key);

final AutocompleteOptionToString<T> displayStringForOption;

final AutocompleteOnSelected<T> onSelected;

final Iterable<T> options;
final double maxOptionsHeight;
final double maxOptionsWidth;

@override
Widget build(BuildContext context) {
return Align(
  alignment: Alignment.topLeft,
  child: Material(
    elevation: 4.0,
    child: ConstrainedBox(
      constraints: BoxConstraints(maxHeight: maxOptionsHeight, maxWidth: maxOptionsWidth), // Added maxWidth which was missing here
      child: ListView.builder(
        padding: EdgeInsets.zero,
        shrinkWrap: true,

Upvotes: 2

il_boga
il_boga

Reputation: 1523

As pointed out here, at the time of writing this, the best option seems to be to alter the BoxConstraints width and height included in the default _AutoCompleteOptions code (that can be found here), and pass it as the optionsViewBuilder argument of Autocomplete. This requires that you are able to calculate the required width in advance, though.

Pasting my code, just for reference:

Autocomplete<Animal>(
  optionsBuilder: (textEditingValue) {
    if (textEditingValue.text == '') {
      return const Iterable<Animal>.empty();
    }
    return animalsList.where((Animal option) {
      return "${option.id}".contains(textEditingValue.text.toLowerCase()) ||
          option.nome!.toLowerCase().contains(textEditingValue.text.toLowerCase()) ||
          option.ownerCF!.toLowerCase().contains(textEditingValue.text.toLowerCase());
    });
  },
  optionsViewBuilder: (context, onSelected, options) {
    return Align(
      alignment: Alignment.topLeft,
      child: Material(
        elevation: 4.0,
        child: ConstrainedBox(
          constraints: const BoxConstraints(maxHeight: 200, maxWidth: 600), //RELEVANT CHANGE: added maxWidth
          child: ListView.builder(
            padding: EdgeInsets.zero,
            shrinkWrap: true,
            itemCount: options.length,
            itemBuilder: (BuildContext context, int index) {
              final Animal option = options.elementAt(index);
              return InkWell(
                onTap: () {
                  onSelected(option);
                },
                child: Builder(builder: (BuildContext context) {
                  final bool highlight = AutocompleteHighlightedOption.of(context) == index;
                  if (highlight) {
                    SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
                      Scrollable.ensureVisible(context, alignment: 0.5);
                    });
                  }
                  return Container(
                    color: highlight ? Theme.of(context).focusColor : null,
                    padding: const EdgeInsets.all(16.0),
                    child: Text(option.getInfo() + " - " + option.getOwnerInfo()),
                  );
                }),
              );
            },
          ),
        ),
      ),
    );
  },
  displayStringForOption: (option) => option.getInfo() + " - " + option.getOwnerInfo(), 
  onSelected: (option) => selected = option,
);

Upvotes: 8

Mahesh Jamdade
Mahesh Jamdade

Reputation: 20369

This is a known bug for Autocomplete widget, the bug is being tracked here: https://github.com/flutter/flutter/issues/78746

So the only solution at the moment is to create your own customized widget. I wrote my own widget and published as a package for reuse and is also being maintained.

you can find it here: https://pub.dev/packages/searchfield

Upvotes: 2

钱宇豪
钱宇豪

Reputation: 75

I have the same issue and resolved it by using a ValueListenableBuilder.

The width of Material widget is depends on width of the widget returned by field ViewBuilder (default is a TextFormField), so one solution is get width of TextFormField after one frame completed and notify your custom optionsViewBuilder

sample code:

import 'package:flutter/material.dart';

class AutocompleteText extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => AutocompleteTextState();
}

class AutocompleteTextState extends State<AutocompleteText> {
  final ValueNotifier<double?> optionsViewWidthNotifier = ValueNotifier(null);

  static const List<User> _userOptions = <User>[
    User(name: 'Alice', email: '[email protected]'),
    User(name: 'Bob', email: '[email protected]'),
    User(name: 'Charlie', email: '[email protected]'),
  ];

  static String _displayStringForOption(User option) => option.name;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Padding(
      padding: EdgeInsets.all(20),
      child: Autocomplete<User>(
        displayStringForOption: _displayStringForOption,
        optionsBuilder: (TextEditingValue textEditingValue) {
          if (textEditingValue.text == '') {
            return const Iterable<User>.empty();
          }
          return _userOptions.where((User option) {
            return option
                .toString()
                .contains(textEditingValue.text.toLowerCase());
          });
        },
        onSelected: (User selection) {
          print('You just selected ${_displayStringForOption(selection)}');
        },
        fieldViewBuilder: (BuildContext context,
            TextEditingController textEditingController,
            FocusNode focusNode,
            VoidCallback onFieldSubmitted) {
          return OrientationBuilder(builder: (context, orientation) {
            WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
              optionsViewWidthNotifier.value =
                  (context.findRenderObject() as RenderBox).size.width;
            });
            return TextFormField(
              controller: textEditingController,
              focusNode: focusNode,
              onFieldSubmitted: (String value) {
                onFieldSubmitted();
              },
            );
          });
        },
        optionsViewBuilder: (
          BuildContext context,
          AutocompleteOnSelected<User> onSelected,
          Iterable<User> options,
        ) {
          return ValueListenableBuilder<double?>(
              valueListenable: optionsViewWidthNotifier,
              builder: (context, width, _child) {
                return Align(
                  alignment: Alignment.topLeft,
                  child: Material(
                    elevation: 4.0,
                    child: SizedBox(
                      width: width,
                      height: 200.0,
                      child: ListView.builder(
                        padding: EdgeInsets.zero,
                        itemCount: options.length,
                        itemBuilder: (BuildContext context, int index) {
                          final User option = options.elementAt(index);
                          return InkWell(
                            onTap: () {
                              onSelected(option);
                            },
                            child: Padding(
                              padding: const EdgeInsets.all(16.0),
                              child: Text(_displayStringForOption(option)),
                            ),
                          );
                        },
                      ),
                    ),
                  ),
                );
              });
        },
      ),
    ));
  }

  @override
  void dispose() {
    optionsViewWidthNotifier.dispose();
    super.dispose();
  }
}

@immutable
class User {
  const User({
    required this.email,
    required this.name,
  });

  final String email;
  final String name;

  @override
  String toString() {
    return '$name, $email';
  }

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is User && other.name == name && other.email == email;
  }

  @override
  int get hashCode => hashValues(email, name);
}

Upvotes: 6

Geoffrey Escobedo
Geoffrey Escobedo

Reputation: 471

I got the same problem while trying to make my own autocomplete widget based on RawAutocomplete and I used a layout builder to get the perfect container width size inside my optionsViewBuilder:

LayoutBuilder(
  builder: (context, constraints) => RawAutocomplete<String>(
    focusNode: focusNode,
    fieldViewBuilder: fieldViewBuilder,
    optionsViewBuilder: (context, onSelected, options) => Align(
      alignment: Alignment.topLeft,
      child: Material(
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(bottom: Radius.circular(4.0)),
        ),
        child: Container(
          height: 52.0 * options.length,
          width: constraints.biggest.width, // <-- Right here !
          child: ListView.builder(
            padding: EdgeInsets.zero,
            itemCount: options.length,
            shrinkWrap: false,
            itemBuilder: (BuildContext context, int index) {
              final String option = options.elementAt(index);
              return InkWell(
                onTap: () => onSelected(option),
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Text(option),
                ),
              );
            },
          ),
        ),
      ),
    ),
    optionsBuilder: (textEditingValue) =>
        suggestions.where((element) => element.contains(textEditingValue.text.trim().toUpperCase())),
    textEditingController: controller,
  ),
)

Result: Expected result on my component

Upvotes: 47

codey-boi
codey-boi

Reputation: 191

I ran into this issue a lot too, and the options builder is very specific about where it wants the size or padding to be placed in the hierarchy. Here's my working example:

optionsViewBuilder: (context, onAutoCompleteSelect, options) {
  return Align(
    alignment: Alignment.topLeft,
       child: Material(
         color: Theme.of(context).primaryColorLight,
           elevation: 4.0,
           // size works, when placed here below the Material widget
           child: Container(
              // I have the text field wrapped in a container with
              // EdgeInsets.all(20) so subtract 40 from the width for the width
              // of the text box. You could also just use a padding widget
              // with EdgeInsets.only(right: 20)
              width: MediaQuery.of(context).size.width - 40,
                child: ListView.separated(
                   shrinkWrap: true,
                   padding: const EdgeInsets.all(8.0),
                   itemCount: options.length,
                   separatorBuilder: (context, i) {
                     return Divider();
                   },
                   itemBuilder: (BuildContext context, int index) {
                     // some child here
                   },
                 )
              ),
           )
        );
     }

Upvotes: 18

Ibrahim Alqayyas
Ibrahim Alqayyas

Reputation: 195

Did you try to put the whole Autocomplete widget in a sized box? If the sizing widgets are not working this probably maybe a bug.

Upvotes: 0

Related Questions