You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

解决方案

问题根源

  1. fieldTextEditingController2未初始化就直接赋值,易引发空指针异常;
  2. onSelected方法中新建TextEditingController,未关联输入框实际控制器,导致值无法同步;
  3. 回调逻辑冗余,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 12:25:24