繁体   English   中英

Flutter 未处理的异常:此小部件已卸载,因此 State 不再具有上下文

[英]Flutter Unhandled Exception: This widget has been unmounted, so the State no longer has a context

我在尝试发布数据时不断收到此错误,解决方案说在调用setState之前检查if(mounted)但我不知道这个setState在哪里? 代码如下; function 在第一个小部件中,然后我在另一个小部件中调用它:

// ignore_for_file: use_build_context_synchronously

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:mne/Forms/form_model.dart';

import 'package:shared_preferences/shared_preferences.dart';

import '../Network/api.dart';

class FormWidget extends StatefulWidget {
  const FormWidget({Key? key}) : super(key: key);

  @override
  State<FormWidget> createState() => FormWidgetState();
}

class FormWidgetState extends State<FormWidget> {
  // to show error message

  _showScaffold(String message) {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text(message, style: const TextStyle(color: Colors.black)),
        duration: const Duration(seconds: 20),
        action: SnackBarAction(
          textColor: Colors.white,
          label: 'Press to go back',
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
        backgroundColor: Colors.redAccent));
  }

  final List<FormModel> _formfields = [];
  var loading = false;
  var isEmpty = false;
  List activityResponses = [];
  late Map<String, List<dynamic>> data;

// to fetch form fields
  fetchFormFields() async {
    setState(() {
      loading = true;
    });

    WidgetsFlutterBinding.ensureInitialized();
    SharedPreferences localStorage = await SharedPreferences.getInstance();

    var saved = localStorage.getString('activitytask_id');

    var res = await Network().getData('mobile/activity-fields/$saved');

    if (res.statusCode == 200) {
      final data = jsonDecode(res.body);
      final tdata = data['data'];
      if (tdata.length == 0) {
        Navigator.of(context).pushReplacementNamed('error');
      }

      var formfieldsJson = tdata;

      setState(() {
        for (Map formfieldJson in formfieldsJson) {
          _formfields.add(FormModel.fromJson(formfieldJson));
        }
        loading = false;
      });
    }
  }

  final TextEditingController _textController = TextEditingController();
  final TextEditingController _numberController2 = TextEditingController();

  @override
  void initState() {
    super.initState();
    fetchFormFields();
  }

  submitResult() async {
    WidgetsFlutterBinding.ensureInitialized();
    SharedPreferences localStorage = await SharedPreferences.getInstance();
    final String? responseData = localStorage.getString('responses');
    final String responseList = json.decode(responseData!);
    final List responseList2 = [responseList];

    debugPrint(responseList.toString());
    data = {"activity_responses": responseList2};
    var res = await Network().authData(data, 'mobile/activity-result');

    if (res.statusCode == 200) {
      Navigator.of(context).pushReplacementNamed('activitytasks');
    } else {
      Navigator.of(context).pushReplacementNamed('error');
    }
  }

  @override
  void dispose() {
    _textController.dispose();
    _numberController2.dispose();

    super.dispose();
  }

  // to display form fields
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            color: Colors.white,
            child: loading
                ? const Center(child: CircularProgressIndicator())
                : ListView.builder(
                    itemCount: _formfields.length,
                    itemBuilder: (context, i) {
                      final nDataList = _formfields[i];

                      return Form(
                          child: Column(children: [
                        Column(children: [
                          if (nDataList.type == 'text')
                            Column(children: [
                              Container(
                                alignment: Alignment.centerLeft,
                                padding:
                                    const EdgeInsets.only(bottom: 5, left: 6),
                                child: Text('Add a ${nDataList.name}',
                                    style: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold)),
                              ),
                              Container(
                                  margin: const EdgeInsets.all(8),
                                  child: TextFormField(
                                    controller: _textController,
                                    decoration: InputDecoration(
                                        contentPadding:
                                            const EdgeInsets.all(15),
                                        border: const OutlineInputBorder(),
                                        filled: true,
                                        fillColor: Colors.grey[200],
                                        labelText: nDataList.name),
                                  )),
                              Container(
                                alignment: Alignment.centerRight,
                                child: ElevatedButton(
                                  style: ButtonStyle(
                                      backgroundColor:
                                          MaterialStateProperty.all<Color>(
                                              Colors.green)),
                                  onPressed: () async {
                                    var responseData = {
                                      "activity_field_id": nDataList.id,
                                      "value": _textController.text
                                    };
                                    activityResponses.add(responseData);
                                    debugPrint(activityResponses.toString());
                                  },
                                  child: const Text('Save',
                                      style: TextStyle(color: Colors.white)),
                                ),
                              ),
                            ]),
                          if (nDataList.type == 'number')
                            Column(children: [
                              Container(
                                alignment: Alignment.centerLeft,
                                padding: const EdgeInsets.only(
                                    bottom: 5, left: 6, top: 5),
                                child: Text('Add a ${nDataList.name}',
                                    style: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold)),
                              ),
                              Container(
                                  margin: const EdgeInsets.all(8),
                                  child: TextFormField(
                                    controller: _numberController2,
                                    decoration: InputDecoration(
                                        contentPadding:
                                            const EdgeInsets.all(15),
                                        border: const OutlineInputBorder(),
                                        filled: true,
                                        fillColor: Colors.grey[200],
                                        labelText: nDataList.name),
                                  )),
                              Container(
                                alignment: Alignment.centerRight,
                                child: ElevatedButton(
                                  style: ButtonStyle(
                                      backgroundColor:
                                          MaterialStateProperty.all<Color>(
                                              Colors.green)),
                                  onPressed: () async {
                                    // valueResponses.add(_numberController2.text);
                                    // idResponses.add(nDataList.id);
                                    var numberData = {
                                      "activity_field_id": nDataList.id,
                                      "value": _numberController2.text
                                    };
                                    activityResponses.add(numberData);
                                    debugPrint(activityResponses.toString());

                                    SharedPreferences localStorage =
                                        await SharedPreferences.getInstance();

                                    final String encodedData =
                                        (activityResponses).asMap().toString();
                                    final String encodedData2 =
                                        jsonEncode(encodedData);
                                    localStorage.setString(
                                        'responses', encodedData2);
                                  },
                                  child: const Text('Save',
                                      style: TextStyle(color: Colors.white)),
                                ),
                              ),
                            ]),
                        ]),
                      ]));
                    })));
  }
}

上面的代码是我编写submitResult()的第一个小部件。 下面的代码用于包含将调用submitResult()的按钮的页面:

import 'package:flutter/material.dart';

import '../Design/custom_shape.dart';
import 'form_widget.dart';

class Workspace extends StatefulWidget {
  const Workspace({Key? key}) : super(key: key);

  @override
  State<Workspace> createState() => WorkspaceState();
}

class WorkspaceState extends State<Workspace> {


  bool isLoading = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      backgroundColor: const Color.fromARGB(255, 236, 246, 219),
      body: Column(
        children: [
          Container(
              alignment: Alignment.center,
              child: Stack(clipBehavior: Clip.none, children: [
                ClipPath(
                    clipper: CustomShape(),
                    child: Container(
                        padding: const EdgeInsets.only(bottom: 128),
                        height: 335,
                        child: Image.asset('assets/images/fields.png',
                            fit: BoxFit.fitWidth))),
                Container(
                  height: 650,
                  width: double.infinity,
                  color: Colors.transparent,
                ),
                Positioned(
                  top: 140,
                  right: 8,
                  height: 600,
                  child: Container(
                      alignment: Alignment.center,
                      width: 360,
                      margin: const EdgeInsets.all(5),
                      padding: const EdgeInsets.all(5),
                      decoration: const BoxDecoration(
                          color: Colors.white,
                          shape: BoxShape.rectangle,
                          borderRadius: BorderRadius.all(Radius.circular(10))),
                      child: Column(
                        children: [
                          Container(
                              height: 450,
                              width: double.infinity,
                              child: const FormWidget()),
                          if (isLoading)
                            Container(
                              alignment: Alignment.bottomCenter,
                              child: ElevatedButton(
                                style: ButtonStyle(
                                    backgroundColor:
                                        MaterialStateProperty.all<Color>(
                                            Colors.green)),
                                onPressed: () async {
                                  setState(() {
                                    isLoading = false;
                                  });
                                  await FormWidgetState().submitResult();
                                  if (!mounted) return;
                                  setState(() {
                                    isLoading = true;
                                  });
                                  // Navigator.of(context).pop();
                                },
                                child: const Text('Submit',
                                    style: TextStyle(color: Colors.white)),
                              ),
                            )
                          else
                            const Center(
                                child: CircularProgressIndicator(
                                    backgroundColor:
                                        Color.fromRGBO(0, 161, 39, 1)))
                        ],
                      )),
                ),
              ])),
        ],
      ),
    );
  }
}

感谢您的帮助

编辑:删除后添加此图像

// ignore_for_file: use_build_context_synchronously

显示错误在哪里。 屏幕截图以显示错误

上面的代码是我编写 submitResult() 的第一个小部件。 下面的代码用于包含将调用 submitResult() 的按钮的页面

出了点问题。 You should not call a method of a State object by simply initialising that State object and calling its method inside a different widget altogether as you did somewhere in the second snippet:

await FormWidgetState().submitResult();

State对象绑定到小部件。 Flutter 不断丢弃并重新创建小部件,但保留其State object。 为了让 Flutter 正确管理State object,绑定的小部件必须是小部件树的一部分。

IDE 显示上述错误,因为该导航器的上下文不是任何小部件树的一部分。 调用 submitResult 时,没有小部件链接到State object。

根据第二个代码片段的排列,您在 Column 中有一个FormWidget 在同一个列中,您有一个 if 块,它有一个带有问题的按钮await FormWidgetState().submitResult(); 在其 onPressed 回调中。

Column(
  children: [
    Container(
        height: 450,
        width: double.infinity,
        child: const FormWidget()),
    if (isLoading)
      Container(
        alignment: Alignment.bottomCenter,
          child: ElevatedButton(
            // ...
              onPressed: () async {
                // ...
                await FormWidgetState().submitResult();
                if (!mounted) return;
                // ...
              },
            child: const Text('Submit',
            style: TextStyle(color: Colors.white)),
          ),
        )
    else
      const Center(
        child: CircularProgressIndicator(
          backgroundColor: Color.fromRGBO(0, 161, 39, 1)))
  ],
),

如果我的猜测是正确的,您希望显示 FormWidget 并使用提交按钮从该 FormWidget 调用submitResult() 这就是为什么你await FormWidgetState().submitResult(); 在 onPressed 回调中。

好吧, await FormWidgetState().submitResult(); line 没有绑定到一个小部件,它是独立的。 它本身没有父级,也不是小部件树的一部分。 因此,这解释了为什么会出现上述错误。 if 块上方 Container 中的FormWidget与您在有问题的await FormWidgetState().submitResult(); 线。 在渲染 UI 时,Flutter 已自动初始化并为上述 FormWidget 链接了一个隐藏的 FormWidgetState object。

事实上, if (;mounted) return; 确实没有我们认为的效果。 因为它自己的包含小部件将始终安装在该方法中。 但是前面有问题的行的mounted吸气剂与这个自己的无关。 (希望你能理解)。

解决方案

解决方案是将 Column 的上下部分合并为一个小部件。 因此 submitResult 调用将与显示的小部件共享相同的上下文(或者更确切地说将具有有效的上下文)。

您有 2 个选项:

  1. 将整个列移动到 FormWidget 内部
  2. 将定义 submitResult 移出 FormWidget 并移到第二个代码片段的文件中。 然后在 FormWidget 的表单填满时调用 submitResult。 您必须找到一种方法在两个小部件之间从外部保存数据(可能使用 state 管理或共享首选项)。

我扩展了选项 1 的细节/步骤如下:

  1. FormWidget采用 isLoading bool 参数。 父级(第二个代码片段)会给它它的值: FormWidget(isLoading)
  2. 将第二个代码片段的 Column 移到第一个代码片段的 build 方法内。 FormWidgetState的构建方法应该返回该列。 您良好的编程技能将指导您完成这一步,因为它有点技术性。 Column 的上半部分应该是 ListView.builder。 Column 当然应该在 Scaffold 的体内。
  3. Column 的下部(if-else 块)应该使用 widget.isLoading 作为 bool 条件。 请记住,这 isLoading 来自父级(步骤 1)。


注意到第一个代码片段的其他一些问题。

查看在 initState() 中调用的initState() fetchFormFields()方法的定义。 你看,那个方法似乎也有出错的可能。 您有一个执行导航但不添加返回语句的 if 块。 因此,如果导航成功,则以下 setState 调用可能是错误的(因为不再安装 FormWidget)。

下面代码中的注释会解释。 我添加了返回语句:

fetchFormFields() async {
  setState(() {
    loading = true;
  });

  WidgetsFlutterBinding.ensureInitialized();
  SharedPreferences localStorage = await SharedPreferences.getInstance();

  var saved = localStorage.getString('activitytask_id');
  var res = await Network().getData('mobile/activity-fields/$saved');

  if (res.statusCode == 200) {
    final data = jsonDecode(res.body);
    final tdata = data['data'];
    if (tdata.length == 0) {
      Navigator.of(context).pushReplacementNamed('error');

      // maybe a return statement is supposed to be found here. 
      // adding return statement here prevents the next setState from being called
      return;
    }

    var formfieldsJson = tdata;

    // because if tdata.length was actually zero, the Navigator would have 
    // navigated out and this widget will no longer be mounted.
    // 
    // setState here would then be called on a "no-longer-existing" widget.
    //
    // But the added return statement in the if block above should prevent this.
    setState(() {
      for (Map formfieldJson in formfieldsJson) {
        _formfields.add(FormModel.fromJson(formfieldJson));
      }
      loading = false;
    });
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM