[英]Flutter error: Unhandled Exception: This widget has been unmounted, so the State no longer has a context
[英]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
采用 isLoading bool 参数。 父级(第二个代码片段)会给它它的值: FormWidget(isLoading)
。FormWidgetState
的构建方法应该返回该列。 您良好的编程技能将指导您完成这一步,因为它有点技术性。 Column 的上半部分应该是 ListView.builder。 Column 当然应该在 Scaffold 的体内。注意到第一个代码片段的其他一些问题。
查看在 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.