Getx 的 setState() 或 markNeedsBuild() 错误

setState() or markNeedsBuild() error with Getx

我正在学习将 Getx 库与 Flutter 一起使用,我遇到了以下问题:我的想法是有一个 TextFields 列表,其中每个都可以水平扩展,即它在屏幕上的占用大小由键入的单词的大小给出,随着单词大小的增加,TextField 的大小也会增加。如果我添加一行,通过FloatingActionButton,没有问题,我可以顺利打字,但是当我添加第二行时,出现如下错误:

════════ Exception caught by widgets library ═══════════════════════════════════ The following assertion was thrown building NewLineKeyword(dirty): setState() or markNeedsBuild() called during build.

This GetX widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase. The widget on which setState() or markNeedsBuild() was called was: GetX<NewLineKeywordController>

controller: null tag: null has builder state: GetXState#ae1d4(controller: Instance of 'NewLineKeywordController') The widget which was currently being built when the offending call was made was: NewLineKeyword dirty The relevant error-causing widget was NewLineKeyword

运行时下图显示:

image of the error

下面是可水平扩展的 TextField 的代码:

class NewLineKeyword extends GetView<NewLineKeywordController>{
  final Key myKey;

  const NewLineKeyword({ 
    Key? key, 
    required this.myKey, 
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final newLineKeywordController = Get.put(NewLineKeywordController());
    newLineKeywordController.setNewLineSize(myKey, 60.0);

    return GetX<NewLineKeywordController>(
      builder: (_){
        return Row(
          children: <Widget>[
            const SizedBox(
              width: 7.5
            ),
            SizedBox(
              width: newLineKeywordController.getNewLineSize(myKey),
              child: TextField(
                autofocus: true,
                minLines: 1,
                maxLines: 1,
                decoration: const InputDecoration(
                  constraints: BoxConstraints(minWidth: 15.0),
                  border: InputBorder.none,
                  contentPadding: EdgeInsets.only(bottom: 13.0),
                  focusedBorder: UnderlineInputBorder(
                    borderSide: BorderSide(color: Colors.blue)
                  )
                ),
                style: const TextStyle(color: Colors.white, fontSize: 20.0),
                onChanged: (String newValue){
                  // get the exact size of the String
                  final TextPainter textPainter = TextPainter(
                    text: TextSpan(text: newValue, style: const TextStyle(fontSize: 20.0)),
                    textDirection: TextDirection.ltr,
                    textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
                  )..layout();

                  newLineKeywordController.changeNewLineSize(myKey, textPainter.size.width + 2.0);
                },
                onSubmitted: (String newValue){
                  // get the exact size of the String
                  final TextPainter textPainter = TextPainter(
                    text: TextSpan(text: newValue, style: const TextStyle(fontSize: 20.0)),
                    textDirection: TextDirection.ltr,
                    textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
                  )..layout();

                  newLineKeywordController.changeNewLineSize(myKey, textPainter.size.width + 2.0);
                }
              )
            )
          ]
        );
      }
    );
  }
}

下面是 TextField Getx 控制器代码:

class NewLineKeywordController extends GetxController{
  final RxMap<Key, double> _mapNewLineSize = <Key, double>{}.obs;

  void setNewLineSize(Key key, double size){
    _mapNewLineSize[key] = size;
  }

  void changeNewLineSize(Key key, double newSize){
    if(_mapNewLineSize[key] != null){
      _mapNewLineSize[key] = newSize;
    }
  }

  double getNewLineSize(Key key){
    if(_mapNewLineSize[key] != null){
      return _mapNewLineSize[key]!;
    }
    else{
      return 10.0;
    }
  }
}

根据所需的关键字,关键字小部件创建所需的小部件(此小部件的控制器为空):

class Keywords extends GetView<KeywordsController>{
  final String reservedKeyword;
  final Key mykey;

  const Keywords({
    Key? key, 
    required this.reservedKeyword,
    required this.mykey
  }) : super(key: key);

  @override
  Widget build(BuildContext context){
    switch(reservedKeyword){
      case "newLine":
        return NewLineKeyword(myKey: mykey);
      //case "second option":
        //return ... 
      //case "third option":
        //return ...
      default:
        return throw NullThrownError();
    }
  }
}

下面的代码对应一行Widget:

class Line extends GetView<LineController> {
  final Key myKey;

  Line({Key? key, required this.myKey}) : super(key: key);

  @override
  final controller = Get.put(LineController());

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: GetX<LineController>(
        builder: (_){
          return Container(
            color: controller.getIsSelected(myKey) ? const Color(0x327a7a7a) : Colors.transparent,
            height: 35.0,
            child: Row(
              children: <Widget>[
                const SizedBox(width: 10.0),
                controller.getKeywords(myKey)
              ]
            )
          );
        }
      ),
      onLongPress: (){
        controller.changeIsSelected(myKey);
      }
    );
  }
}

线条小部件控制器的代码:

class LineController extends GetxController{
  final RxMap<Key, Keywords> _mapKeywords = <Key, Keywords>{}.obs;

  void setKeywords(Key key, Keywords keyword){
    _mapKeywords[key] = keyword;
  }

  void changeKeywords(Key key, Keywords newKeyword){
    if(_mapKeywords[key] != null){
      _mapKeywords[key] = newKeyword;
    }
  }

  Keywords getKeywords(Key key){
    if(_mapKeywords[key] != null){
      return _mapKeywords[key]!;
    }
    else{
      return Keywords(reservedKeyword: "newLine", mykey: key);
    }
  }
  // set, get and change for isSelected
}

下面的编辑器小部件是线路列表所在的位置:

class Editor extends GetView<EditorController> {
  const Editor({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    ScrollController scrollCode = ScrollController();

    final editorController = Get.put(EditorController());

    List<Line> lineList = editorController.getLineList();

    return Padding(
      padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 120.0),
      child: GetX<EditorController>(
        builder: (_){
          return DraggableScrollbar.arrows(
            controller: scrollCode,
            child: ListView.builder(
              controller: scrollCode,
              itemCount: lineList.length,
              itemBuilder: (BuildContext context, int index){
                return lineList[index];
              }
            )
          );
        }
      )
    );
  }
}

编辑器 Getx 控制器:

class EditorController extends GetxController{
  final RxList<Line> _codeList = <Line>[].obs;

  void addLine(Line value){
    _codeList.add(value);
  }

  List<Line> getLineList(){
    return _codeList;
  }

}

下面是在编辑器中创建新行的按钮:

class ExpandableFab extends StatelessWidget {

  const ExpandableFab({ Key? key }) : super(key: key);

   @override
   Widget build(BuildContext context) {
    final lineController = Get.put(LineController());
    final editorController = Get.put(EditorController());

    Key mykey;

    return FloatingActionButton(
      child: const Icon(
        Icons.add, 
      ),
      onPressed: (){
        mykey = GlobalKey();
        lineController.setIsSelected(mykey, false);
        lineController.setKeywords(mykey, Keywords(reservedKeyword: "newLine", mykey: mykey));
        editorController.addLine(Line(myKey: mykey));
      }
    );
  }
}

我不太理解这个错误,因为它们是同一小部件​​的不同实例,因此可以毫无问题地创建它。我真的迷失在这个问题中,在这种情况下如何创建多个小部件?

这是因为您试图在构建小部件树时更新可观察值, 这可能是由于 newLineKeywordController.setNewLineSize(myKey, 60.0);

方法造成的

要解决此问题,您可以将 setNewLineSize 方法更改为:

void setNewLineSize(Key key, double size) async {
    await Future.delayed(Duration.zero);
    _mapNewLineSize[key] = size;
}