Flutter跨类自定义Autocomplete组件的值获取问题
Flutter自动补全组件值无法获取的解决办法
问题说明
我是Flutter新手,已花费数小时寻找以下问题的解决方案,但搜索到的方案均无效。我在ItemDetails页面中使用了自动补全文本输入组件,该组件被封装在独立类LabelSugg中,添加到主组件后可正常运行,但无法获取其值以与其他字段一同存储。
现有代码
ItemDetails页面代码
class ItemDetails extends StatefulWidget { const ItemDetails({Key? key}) : super(key: key); static const routeName = '/item_details'; @override State<ItemDetails> createState() => _ItemDetails(); } class _ItemDetails extends State<ItemDetails> { late TextEditingController labelController; late TextEditingController valueController; late TextEditingController notesController; bool _submitted = false; late var args; String _itemLabel2 = ""; // var labelAutoComp = LabelSugg(); @override void initState() { super.initState(); labelController = TextEditingController(); valueController = TextEditingController(); notesController = TextEditingController(); } @override void dispose() { labelController.dispose(); valueController.dispose(); notesController.dispose(); // Hive.close(); super.dispose(); } String? _labelErrorText(context) { final text = labelController.value.text; if (text.isEmpty) { // return 'Can\'t be empty'; return AppLocalizations.of(context)!.noEmpty; } } String? _valueErrorText(context) { final text = valueController.value.text; if (text.isEmpty) { // return 'Can\'t be empty'; return AppLocalizations.of(context)!.noEmpty; } } @override Widget build(BuildContext context) { try { args = ModalRoute.of(context)!.settings.arguments as Map; } on Exception catch (e) { // print(e); } // print(args); return Scaffold( appBar: AppBar( title: Text(args['title']), ), body: Container( padding: const EdgeInsets.all(20), child: Column( children: <Widget>[ Padding( padding: const EdgeInsets.all(20), child: Column( // mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ LabelSugg(getLabelText: (String val) { print(val); _itemLabel2 = val; }), TextField( autofocus: true, decoration: InputDecoration( labelText: AppLocalizations.of(context)!.label, hintText: AppLocalizations.of(context)!.labelHint, errorText: _submitted ? _labelErrorText(context) : null, ), controller: labelController, onChanged: (_) => setState(() {}), ), const SizedBox(height: 5), TextField( autofocus: false, decoration: InputDecoration( labelText: AppLocalizations.of(context)!.value, hintText: AppLocalizations.of(context)!.valueHint, errorText: _submitted ? _valueErrorText(context) : null, ), controller: valueController, keyboardType: const TextInputType.numberWithOptions( decimal: true, signed: false), inputFormatters: <TextInputFormatter>[ FilteringTextInputFormatter.allow( RegExp(r"[0-9.]")), TextInputFormatter.withFunction( (oldValue, newValue) { try { final text = newValue.text; if (text.isNotEmpty) double.parse(text); return newValue; } catch (e) {} return oldValue; }), ], // Only numbers can be entered onChanged: (_) => setState(() {}), ), const SizedBox(height: 5), TextField( autofocus: true, decoration: InputDecoration( labelText: AppLocalizations.of(context)!.notes, hintText: AppLocalizations.of(context)!.noteHint, ), controller: notesController, onChanged: (_) => setState(() {}), ), ]), // ], ), Expanded( child: Align( alignment: FractionalOffset.bottomCenter, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton.icon( onPressed: () { setState(() => _submitted = true); if (_labelErrorText(context) == null && _valueErrorText(context) == null) { //insert var localLabel = labelController.value.text; var _localValue = 0.0; if (valueController.value.text != '') { _localValue = double.parse(valueController.value.text); } else { _localValue = 0.0; } var localNotes = notesController.value.text; addItemToList( localLabel, _localValue, localNotes); Navigator.of(context).pop(); labelController.clear(); valueController.clear(); notesController.clear(); } }, label: Text(AppLocalizations.of(context)!.add), icon: const Icon(Icons.save, size: 18), ), const SizedBox(width: 10), ElevatedButton.icon( onPressed: () => {Navigator.pop(context)}, label: Text(AppLocalizations.of(context)!.cancel), icon: const Icon(Icons.cancel, size: 18), ), ], )), ), // ) ], ))); } void addItemToList(String localLabel, double localValue, String localNotes) { var _item = YearItems()..yearID = args['year']; _item.itemLabel = localLabel; _item.itemValue = localValue; _item.itemNote = localNotes; print(_itemLabel2); final itemsBox = ItemsBoxes.getTransactions(); itemsBox.add(_item); } }
LabelSugg组件代码
class LabelSugg extends StatefulWidget { final ValueChanged<String> getLabelText; const LabelSugg({Key? key, required this.getLabelText}) : super(key: key); @override State<LabelSugg> createState() => _LabelSugg(); } class _LabelSugg extends State<LabelSugg> { late TextEditingController fieldTextEditingController2; @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } getLabel() { return widget.getLabelText(fieldTextEditingController2.text); } @override Widget build(BuildContext context) { List<LabelsAc> labelOptions = <LabelsAc>[ LabelsAc(label: AppLocalizations.of(context)!.labelClothes), LabelsAc(label: AppLocalizations.of(context)!.labelFood), LabelsAc(label: AppLocalizations.of(context)!.labelPerfumes), LabelsAc(label: AppLocalizations.of(context)!.labelCapital), ]; return Autocomplete<LabelsAc>( optionsBuilder: (TextEditingValue textEditingValue) { return labelOptions .where((LabelsAc _label) => _label.label .toLowerCase() .startsWith(textEditingValue.text.toLowerCase())) .toList(); }, displayStringForOption: (LabelsAc option) => option.label, fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, // fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { return TextField( controller: fieldTextEditingController, focusNode: fieldFocusNode, style: const TextStyle(fontWeight: FontWeight.bold), // onChanged: getLabel(), onChanged: (String val) { fieldTextEditingController2 = fieldTextEditingController; getLabel(); }); }, onSelected: (LabelsAc selection) { fieldTextEditingController2 = TextEditingController(text: selection.label); getLabel(); }, optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<LabelsAc> onSelected, Iterable<LabelsAc> options) { return Align( alignment: Alignment.topLeft, child: Material( child: Container( // width: 350, // color: Colors.cyan, child: ListView.builder( padding: const EdgeInsets.all(10.0), itemCount: options.length, itemBuilder: (BuildContext context, int index) { final LabelsAc option = options.elementAt(index); return GestureDetector( onTap: () { onSelected(option); }, child: ListTile( title: Text(option.label, style: const TextStyle(color: Colors.black)), ), ); }, ), ), ), ); }, ); // ), // ); } } class LabelsAc { LabelsAc({required this.label}); String label; }
解决方案
问题根源
fieldTextEditingController2未初始化就直接赋值,易引发空指针异常;onSelected方法中新建TextEditingController,未关联输入框实际控制器,导致值无法同步;- 回调逻辑冗余,
getLabel()方法无需单独封装,直接调用widget.getLabelText()即可。
修改步骤
1. 修复LabelSugg组件
class _LabelSugg extends State<LabelSugg> { late TextEditingController fieldTextEditingController2; @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { List<LabelsAc> labelOptions = <LabelsAc>[ LabelsAc(label: AppLocalizations.of(context)!.labelClothes), LabelsAc(label: AppLocalizations.of(context)!.labelFood), LabelsAc(label: AppLocalizations.of(context)!.labelPerfumes), LabelsAc(label: AppLocalizations.of(context)!.labelCapital), ]; return Autocomplete<LabelsAc>( optionsBuilder: (TextEditingValue textEditingValue) { return labelOptions .where((LabelsAc _label) => _label.label .toLowerCase() .startsWith(textEditingValue.text.toLowerCase())) .toList(); }, displayStringForOption: (LabelsAc option) => option.label, fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { // 初始化控制器 fieldTextEditingController2 = fieldTextEditingController; return TextField( controller: fieldTextEditingController, focusNode: fieldFocusNode, style: const TextStyle(fontWeight: FontWeight.bold), onChanged: (String val) { widget.getLabelText(val); }); }, onSelected: (LabelsAc selection) { // 直接更新现有控制器文本,避免新建 fieldTextEditingController2.text = selection.label; widget.getLabelText(selection.label); }, optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<LabelsAc> onSelected, Iterable<LabelsAc> options) { return Align( alignment: Alignment.topLeft, child: Material( child: Container( child: ListView.builder( padding: const EdgeInsets.all(10.0), itemCount: options.length, itemBuilder: (BuildContext context, int index) { final LabelsAc option = options.elementAt(index); return GestureDetector( onTap: () { onSelected(option); }, child: ListTile( title: Text(option.label, style: const TextStyle(color: Colors.black)), ), ); }, ), ), ), ); }, ); } }
2. 修改ItemDetails的存储逻辑
在addItemToList方法中,替换为自动补全组件的值:
void addItemToList(String localLabel, double localValue, String localNotes) { var _item = YearItems()..yearID = args['year']; _item.itemLabel = _itemLabel2; // 使用自动补全组件的值 _item.itemValue = localValue; _item.itemNote = localNotes; print(_itemLabel2); final itemsBox = ItemsBoxes.getTransactions(); itemsBox.add(_item); }
完成以上修改后,即可正常获取LabelSugg组件的值,并与其他字段一同存储。
内容的提问来源于stack exchange,提问作者Mohammed Al Mobairik
相关产品推荐
相关产品推荐

