Unverified Commit 884129d1 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

Autocomplete and RawAutocomplete initialValue parameter (#80257)

parent a4411b55
......@@ -167,6 +167,7 @@ class Autocomplete<T extends Object> extends StatelessWidget {
this.fieldViewBuilder = _defaultFieldViewBuilder,
this.onSelected,
this.optionsViewBuilder,
this.initialValue,
}) : assert(displayStringForOption != null),
assert(optionsBuilder != null),
super(key: key);
......@@ -192,6 +193,9 @@ class Autocomplete<T extends Object> extends StatelessWidget {
/// default.
final AutocompleteOptionsViewBuilder<T>? optionsViewBuilder;
/// {@macro flutter.widgets.RawAutocomplete.initialValue}
final TextEditingValue? initialValue;
static Widget _defaultFieldViewBuilder(BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
return _AutocompleteField(
focusNode: focusNode,
......@@ -205,6 +209,7 @@ class Autocomplete<T extends Object> extends StatelessWidget {
return RawAutocomplete<T>(
displayStringForOption: displayStringForOption,
fieldViewBuilder: fieldViewBuilder,
initialValue: initialValue,
optionsBuilder: optionsBuilder,
optionsViewBuilder: optionsViewBuilder ?? (BuildContext context, AutocompleteOnSelected<T> onSelected, Iterable<T> options) {
return _AutocompleteOptions<T>(
......
......@@ -493,6 +493,7 @@ class RawAutocomplete<T extends Object> extends StatefulWidget {
this.focusNode,
this.onSelected,
this.textEditingController,
this.initialValue,
}) : assert(displayStringForOption != null),
assert(
fieldViewBuilder != null
......@@ -502,6 +503,10 @@ class RawAutocomplete<T extends Object> extends StatefulWidget {
assert(optionsBuilder != null),
assert(optionsViewBuilder != null),
assert((focusNode == null) == (textEditingController == null)),
assert(
!(textEditingController != null && initialValue != null),
'textEditingController and initialValue cannot be simultaneously defined.'
),
super(key: key);
/// {@template flutter.widgets.RawAutocomplete.fieldViewBuilder}
......@@ -661,6 +666,16 @@ class RawAutocomplete<T extends Object> extends StatefulWidget {
/// If this parameter is not null, then [focusNode] must also be not null.
final TextEditingController? textEditingController;
/// {@template flutter.widgets.RawAutocomplete.initialValue}
/// The initial value to use for the text field.
/// {@endtemplate}
///
/// Setting the initial value does not notify [textEditingController]'s
/// listeners, and thus will not cause the options UI to appear.
///
/// This parameter is ignored if [textEditingController] is defined.
final TextEditingValue? initialValue;
/// Calls [AutocompleteFieldViewBuilder]'s onFieldSubmitted callback for the
/// RawAutocomplete widget indicated by the given [GlobalKey].
///
......@@ -811,7 +826,7 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
@override
void initState() {
super.initState();
_textEditingController = widget.textEditingController ?? TextEditingController();
_textEditingController = widget.textEditingController ?? TextEditingController.fromValue(widget.initialValue);
_textEditingController.addListener(_onChangedField);
_focusNode = widget.focusNode ?? FocusNode();
_focusNode.addListener(_onChangedFocus);
......
......@@ -253,4 +253,51 @@ void main() {
await tester.pump();
expect(find.byKey(optionsKey), findsOneWidget);
});
testWidgets('initialValue sets initial text field value', (WidgetTester tester) async {
late String lastSelection;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Autocomplete<String>(
initialValue: const TextEditingValue(text: 'lem'),
onSelected: (String selection) {
lastSelection = selection;
},
optionsBuilder: (TextEditingValue textEditingValue) {
return kOptions.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
),
),
),
);
// The field is always rendered, but the options are not unless needed.
expect(find.byType(TextFormField), findsOneWidget);
expect(find.byType(ListView), findsNothing);
expect(
tester.widget<TextFormField>(find.byType(TextFormField)).controller!.text,
'lem',
);
// Focus the empty field. All the options are displayed.
await tester.tap(find.byType(TextFormField));
await tester.pump();
expect(find.byType(ListView), findsOneWidget);
final ListView list = find.byType(ListView).evaluate().first.widget as ListView;
// Displays just one option ('lemur').
expect(list.semanticChildCount, 1);
// Select a option. The options hide and the field updates to show the
// selection.
await tester.tap(find.byType(InkWell).first);
await tester.pump();
expect(find.byType(TextFormField), findsOneWidget);
expect(find.byType(ListView), findsNothing);
final TextFormField field = find.byType(TextFormField).evaluate().first.widget as TextFormField;
expect(field.controller!.text, 'lemur');
expect(lastSelection, 'lemur');
});
}
......@@ -102,7 +102,7 @@ void main() {
expect(lastOptions.elementAt(0), 'chameleon');
expect(lastOptions.elementAt(1), 'elephant');
// Select a option. The options hide and the field updates to show the
// Select an option. The options hide and the field updates to show the
// selection.
final String selection = lastOptions.elementAt(1);
lastOnSelected(selection);
......@@ -184,7 +184,7 @@ void main() {
expect(lastOptions.elementAt(0), kOptionsUsers[0]);
expect(lastOptions.elementAt(1), kOptionsUsers[1]);
// Select a option. The options hide and onSelected is called.
// Select an option. The options hide and onSelected is called.
final User selection = lastOptions.elementAt(1);
lastOnSelected(selection);
await tester.pump();
......@@ -266,7 +266,7 @@ void main() {
expect(lastOptions.elementAt(0), kOptionsUsers[0]);
expect(lastOptions.elementAt(1), kOptionsUsers[1]);
// Select a option. The options hide and onSelected is called. The field
// Select an option. The options hide and onSelected is called. The field
// has its text set to the selection's display string.
final User selection = lastOptions.elementAt(1);
lastOnSelected(selection);
......@@ -553,4 +553,96 @@ void main() {
expect(find.byKey(optionsKey), findsNothing);
expect(textEditingController.text, lastOptions.elementAt(0));
});
testWidgets('initialValue sets initial text field value', (WidgetTester tester) async {
final GlobalKey fieldKey = GlobalKey();
final GlobalKey optionsKey = GlobalKey();
late Iterable<String> lastOptions;
late AutocompleteOnSelected<String> lastOnSelected;
late FocusNode focusNode;
late TextEditingController textEditingController;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: RawAutocomplete<String>(
// Should initialize text field with 'lem'.
initialValue: const TextEditingValue(text: 'lem'),
optionsBuilder: (TextEditingValue textEditingValue) {
return kOptions.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) {
focusNode = fieldFocusNode;
textEditingController = fieldTextEditingController;
return TextField(
key: fieldKey,
focusNode: focusNode,
controller: textEditingController,
);
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
lastOptions = options;
lastOnSelected = onSelected;
return Container(key: optionsKey);
},
),
),
),
);
// The field is always rendered, but the options are not unless needed.
expect(find.byKey(fieldKey), findsOneWidget);
expect(find.byKey(optionsKey), findsNothing);
// The text editing controller value starts off with initialized value.
expect(textEditingController.text, 'lem');
// Focus the empty field. All the options are displayed.
focusNode.requestFocus();
await tester.pump();
expect(find.byKey(optionsKey), findsOneWidget);
expect(lastOptions.elementAt(0), 'lemur');
// Select an option. The options hide and the field updates to show the
// selection.
final String selection = lastOptions.elementAt(0);
lastOnSelected(selection);
await tester.pump();
expect(find.byKey(fieldKey), findsOneWidget);
expect(find.byKey(optionsKey), findsNothing);
expect(textEditingController.text, selection);
});
testWidgets('initialValue cannot be defined if TextEditingController is defined', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
final TextEditingController textEditingController = TextEditingController();
expect(
() {
RawAutocomplete<String>(
focusNode: focusNode,
// Both [initialValue] and [textEditingController] cannot be
// simultaneously defined.
initialValue: const TextEditingValue(text: 'lemur'),
textEditingController: textEditingController,
optionsBuilder: (TextEditingValue textEditingValue) {
return kOptions.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
return Container();
},
fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) {
return TextField(
focusNode: focusNode,
controller: textEditingController,
);
},
);
},
throwsAssertionError,
);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment