Autocomplete and RawAutocomplete initialValue parameter (#80257)
This commit is contained in:
parent
a4411b5565
commit
884129d156
@ -167,6 +167,7 @@ class Autocomplete<T extends Object> extends StatelessWidget {
|
|||||||
this.fieldViewBuilder = _defaultFieldViewBuilder,
|
this.fieldViewBuilder = _defaultFieldViewBuilder,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
this.optionsViewBuilder,
|
this.optionsViewBuilder,
|
||||||
|
this.initialValue,
|
||||||
}) : assert(displayStringForOption != null),
|
}) : assert(displayStringForOption != null),
|
||||||
assert(optionsBuilder != null),
|
assert(optionsBuilder != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
@ -192,6 +193,9 @@ class Autocomplete<T extends Object> extends StatelessWidget {
|
|||||||
/// default.
|
/// default.
|
||||||
final AutocompleteOptionsViewBuilder<T>? optionsViewBuilder;
|
final AutocompleteOptionsViewBuilder<T>? optionsViewBuilder;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.RawAutocomplete.initialValue}
|
||||||
|
final TextEditingValue? initialValue;
|
||||||
|
|
||||||
static Widget _defaultFieldViewBuilder(BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
|
static Widget _defaultFieldViewBuilder(BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
|
||||||
return _AutocompleteField(
|
return _AutocompleteField(
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
@ -205,6 +209,7 @@ class Autocomplete<T extends Object> extends StatelessWidget {
|
|||||||
return RawAutocomplete<T>(
|
return RawAutocomplete<T>(
|
||||||
displayStringForOption: displayStringForOption,
|
displayStringForOption: displayStringForOption,
|
||||||
fieldViewBuilder: fieldViewBuilder,
|
fieldViewBuilder: fieldViewBuilder,
|
||||||
|
initialValue: initialValue,
|
||||||
optionsBuilder: optionsBuilder,
|
optionsBuilder: optionsBuilder,
|
||||||
optionsViewBuilder: optionsViewBuilder ?? (BuildContext context, AutocompleteOnSelected<T> onSelected, Iterable<T> options) {
|
optionsViewBuilder: optionsViewBuilder ?? (BuildContext context, AutocompleteOnSelected<T> onSelected, Iterable<T> options) {
|
||||||
return _AutocompleteOptions<T>(
|
return _AutocompleteOptions<T>(
|
||||||
|
@ -493,6 +493,7 @@ class RawAutocomplete<T extends Object> extends StatefulWidget {
|
|||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
this.textEditingController,
|
this.textEditingController,
|
||||||
|
this.initialValue,
|
||||||
}) : assert(displayStringForOption != null),
|
}) : assert(displayStringForOption != null),
|
||||||
assert(
|
assert(
|
||||||
fieldViewBuilder != null
|
fieldViewBuilder != null
|
||||||
@ -502,6 +503,10 @@ class RawAutocomplete<T extends Object> extends StatefulWidget {
|
|||||||
assert(optionsBuilder != null),
|
assert(optionsBuilder != null),
|
||||||
assert(optionsViewBuilder != null),
|
assert(optionsViewBuilder != null),
|
||||||
assert((focusNode == null) == (textEditingController == null)),
|
assert((focusNode == null) == (textEditingController == null)),
|
||||||
|
assert(
|
||||||
|
!(textEditingController != null && initialValue != null),
|
||||||
|
'textEditingController and initialValue cannot be simultaneously defined.'
|
||||||
|
),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
/// {@template flutter.widgets.RawAutocomplete.fieldViewBuilder}
|
/// {@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.
|
/// If this parameter is not null, then [focusNode] must also be not null.
|
||||||
final TextEditingController? textEditingController;
|
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
|
/// Calls [AutocompleteFieldViewBuilder]'s onFieldSubmitted callback for the
|
||||||
/// RawAutocomplete widget indicated by the given [GlobalKey].
|
/// RawAutocomplete widget indicated by the given [GlobalKey].
|
||||||
///
|
///
|
||||||
@ -811,7 +826,7 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_textEditingController = widget.textEditingController ?? TextEditingController();
|
_textEditingController = widget.textEditingController ?? TextEditingController.fromValue(widget.initialValue);
|
||||||
_textEditingController.addListener(_onChangedField);
|
_textEditingController.addListener(_onChangedField);
|
||||||
_focusNode = widget.focusNode ?? FocusNode();
|
_focusNode = widget.focusNode ?? FocusNode();
|
||||||
_focusNode.addListener(_onChangedFocus);
|
_focusNode.addListener(_onChangedFocus);
|
||||||
|
@ -253,4 +253,51 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.byKey(optionsKey), findsOneWidget);
|
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(0), 'chameleon');
|
||||||
expect(lastOptions.elementAt(1), 'elephant');
|
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.
|
// selection.
|
||||||
final String selection = lastOptions.elementAt(1);
|
final String selection = lastOptions.elementAt(1);
|
||||||
lastOnSelected(selection);
|
lastOnSelected(selection);
|
||||||
@ -184,7 +184,7 @@ void main() {
|
|||||||
expect(lastOptions.elementAt(0), kOptionsUsers[0]);
|
expect(lastOptions.elementAt(0), kOptionsUsers[0]);
|
||||||
expect(lastOptions.elementAt(1), kOptionsUsers[1]);
|
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);
|
final User selection = lastOptions.elementAt(1);
|
||||||
lastOnSelected(selection);
|
lastOnSelected(selection);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -266,7 +266,7 @@ void main() {
|
|||||||
expect(lastOptions.elementAt(0), kOptionsUsers[0]);
|
expect(lastOptions.elementAt(0), kOptionsUsers[0]);
|
||||||
expect(lastOptions.elementAt(1), kOptionsUsers[1]);
|
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.
|
// has its text set to the selection's display string.
|
||||||
final User selection = lastOptions.elementAt(1);
|
final User selection = lastOptions.elementAt(1);
|
||||||
lastOnSelected(selection);
|
lastOnSelected(selection);
|
||||||
@ -553,4 +553,96 @@ void main() {
|
|||||||
expect(find.byKey(optionsKey), findsNothing);
|
expect(find.byKey(optionsKey), findsNothing);
|
||||||
expect(textEditingController.text, lastOptions.elementAt(0));
|
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,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user