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: 'alice@example.com'),
User(name: 'Bob', email: 'bob@example.com'),
User(name: 'Charlie', email: 'charlie123@gmail.com'),
];
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);
}