调用 setState 时,我的有状态小部件不会更新其状态
Posted
技术标签:
【中文标题】调用 setState 时,我的有状态小部件不会更新其状态【英文标题】:My stateful widget does not update it's state when setState is called 【发布时间】:2021-11-17 03:19:18 【问题描述】:背景
在我的有状态小部件的状态对象中,我有以下代码。
class _PendingJobsState extends State<PendingJobs>
List<String> pendingJobs = []; <------------------- I am trying to change the state of this variable.
void updateListWithResponseData(List jobs)
BackendApi.call(
endpoint: APIEndPoints.GET_PENDING_JOBS,
data: "email": GlobalData.userEmail,
onSuccess: (data)
setState(() <---------------- I call set State here
jobs = data['job_list'];
print(pendingJobs); <------------ State is not changed
print(jobs);
);
,
onFailed: (error)
print('Failed to fetch data!');
);
@override
void initState()
updateListWithResponseData(pendingJobs); <------- This is where the function in which I call the setState is executed.
super.initState();
关于上述代码的详细信息
???? List<String> pendingJobs = [];
是我期望完成状态更改的变量。
????在上述变量updateListWithResponseData
正下方定义的函数采用List
类型的参数。它还负责调用另一个名为BackendApi.call()
的实用程序函数。
????我在initState
中调用udateListWithResponseData()
,对于List
类型的参数,我给出了我定义的pendingJobs
变量。 (由于我是从updateListWithResponseData()
函数中调用setState
,我希望pendingJobs
的状态在调用updateListWithResponseData
时会发生变化。)
???? 但是,我在上面所期望的状态变化并没有发生。
???? BackendApi.call
负责从给定的 url 中获取数据,它为 onSuccess
和 onFailure
提供两个回调函数,它们负责根据数据获取是否成功执行必要的操作。
重要说明
从updateListWithResponseData
中删除List jobs
参数并直接引用pendingJobs
变量对我来说是不是解决方案,因为我希望提取调用 updateListWithResponseData
的函数到一个单独的 dart 文件并从不同的小部件调用它。 所以我必须在函数中有 List jobs
参数。
我尝试调试此问题一段时间,但找不到解决方案。如果有人能指出为什么pendingJobs
的状态没有改变以及如何真正改变它,那将非常有帮助。 (谢谢。)
**编辑**
由于下面的许多 cmets 似乎都围绕 BackendApi.call()
函数,并且由于我没有在我的原始帖子中包含该函数的代码,所以我编辑了这篇文章以包含该代码。
import 'dart:convert';
import 'package:field_app/globalData/global_data.dart';
import 'package:http/http.dart' as http;
import 'api_endpoints.dart';
typedef ValueChanged<T> = void Function(T value);
class BackendApi
static void call(
String endpoint,
Map<String, dynamic> data,
ValueChanged<Map<String, dynamic>> onSuccess,
ValueChanged<String> onFailed) async
try
var response = await http.post(APIEndPoints.API_ROOT + endpoint,
headers:
"Content-Type": "application/json",
"Authorization": 'Bearer ' + GlobalData.authToken,
,
body: jsonEncode(data));
Map<String, dynamic> apiResponse = jsonDecode(response.body);
if (apiResponse != null)
if (response.statusCode == 200)
if (onFailed != null)
onSuccess(apiResponse);
else
print(apiResponse['message']);
print('code: ' + response.statusCode.toString());
if (onFailed != null)
onFailed(apiResponse['message']);
else
print('Invalid API response format');
print('code: ' + response.statusCode.toString());
return null;
catch (e)
print("Failed to connect with backend API");
print(e.toString());
return null;
【问题讨论】:
抱歉,BackendApi.call
是在使用 http 请求还是在使用某种 Future?
@Yeasin Sheikh 是的。 Backend.call
是一个异步函数,它发送 post http request
你能不能尝试使updateListWithResponseData()
异步并使用await。
在setState
中,您应该更改pendingJobs
的值,而不是jobs
。顺便说一句,FutureBuilder
是一个更好的解决方案,请参阅我的回答 here。
@YeasinSheikh 这样做有一点问题,因为BackendApi.call()
的返回类型为void
。所以我不能 await
它即使我将 updateListWithResponseData()
设为异步函数。
【参考方案1】:
这种行为背后的原因是 dart 参数是按值传递的。 (即变量的副本与变量数据一起传递)
因此,您在这里传递了pendingJobs
中值的副本,这恰好是对列表的引用。
@override
void initState()
updateListWithResponseData(pendingJobs); <------- This is where the function in which I call the setState is executed.
super.initState();
现在updateListWithResponseData
拥有自己的变量jobs
,其中包含pendingJobs
引用的副本
Future<void> updateListWithResponseData(List jobs) async
await BackendApi.call(
endpoint: APIEndPoints.GET_PENDING_JOBS,
data: "email": GlobalData.userEmail,
onSuccess: (data)
setState(() <---------------- I call set State here
pendingJobs = data['job_list'];
print(pendingJobs); <------------ State is not changed
print(jobs);
);
,
onFailed: (error)
print('Failed to fetch data!');
);
所以jobs = data['job_list'];
所做的是将局部变量(给updateListWithResponseData
)分配给jobs
值,此更改不会反映在pendingJobs
上,因为您只是在updateListWithResponseData
内更新副本。
要解决此问题,请删除分配 jobs = data['job_list'];
并将其替换为 jobs.addAll(data['job_list']);
这样pendingJobs
的值也会得到更新。
【讨论】:
非常感谢。你的回答对我有用。我想从您的回答中澄清一件事。在您的回答中,您提到了to fix this you remove the assignment jobs = data['job_list']; and replace it with jobs.addAll(data['job_list']); this way pendingJobs value will get updated too.
。现在既然updateListWithResponseData
有它自己的变量jobs
,那么jobs.addAll(data['job_list']);
怎么会更新我们想要的实际pendingJobs
变量而不是更新作用域为函数的局部变量?
我想我已经找到了我输入的上述评论的答案。 jobs = data['job_list']
将对 data[job_list]
的引用从 pendingJobs
更改。但是,当我们执行jobs.addAll[data['job_list']]
时,我们不使用assignment operator
。因此对pendingJobs
的引用没有改变,它对pendingJobs
进行了更改
没错。这正是发生的情况,这是“按值传递”的预期行为,因为被调用函数接收值的副本(在您的情况下是对列表的引用)并将其存储为局部变量,并且当您分配该变量的任何值都会丢失调用者传递的值,从而失去修改它的可能性。【参考方案2】:
第一件事:如果你想做一个在第一次构建时会改变状态的异步任务,总是在 WidgetsBinding.instance.addPostFrameCallback 中做。 第二件事:您不必使用状态变量作为状态方法的参数。
试试这个:
class _PendingJobsState extends State<PendingJobs>
List<String> pendingJobs = []; <------------------- I am trying to change the state of this variable.
void updateListWithResponseData()
BackendApi.call(
endpoint: APIEndPoints.GET_PENDING_JOBS,
data: "email": GlobalData.userEmail,
onSuccess: (data)
setState(() <---------------- I call set State here
pendingJobs = data['job_list'];
print(pendingJobs);
);
,
onFailed: (error)
print('Failed to fetch data!');
);
@override
void initState()
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => updateListWithResponseData()) <------- This is where the function in which I call the setState is executed.
【讨论】:
【参考方案3】:试试:
Future<void> updateListWithResponseData(List jobs) async
await BackendApi.call(
endpoint: APIEndPoints.GET_PENDING_JOBS,
data: "email": GlobalData.userEmail,
onSuccess: (data)
setState(() <---------------- I call set State here
pendingJobs = data['job_list'];
print(pendingJobs); <------------ State is not changed
print(jobs);
);
,
onFailed: (error)
print('Failed to fetch data!');
);
在initState
:
updateListWithResponseData(pendingJobs);
这是该工作流程的演示小部件
class ApplicantsX extends StatefulWidget
const ApplicantsX(Key? key) : super(key: key);
@override
State<ApplicantsX> createState() => _ApplicantsXState();
class _ApplicantsXState extends State<ApplicantsX>
int a = 0;
Future<void> up(int v) async
await Future.delayed(Duration(seconds: 2), ()
setState(()
a = v;
);
);
@override
void initState()
super.initState();
up(234);
@override
Widget build(BuildContext context)
return Center(child: Text("$a"));
【讨论】:
感谢您的回答和努力。我运行了这段代码,但仍然没有更新更改,而且只要我输入术语await
,我的编辑就会给我一个警告说await applied to void which is not a future
是的。在Futue<void>
之后它仍然存在。我认为这是因为BackendApi.call()
没有返回Future
。因此,我认为等待它会发出警告....
您能否包含 BackendApi.call
,我认为它应该返回 Future<void>
而不是 void,因为我们在 HTTP 上请求并且它的响应将是未来的?
我试过这个。但是状态仍然没有改变......我认为制作BackendApi.call
,Future<void>
是没有必要的,因为该api util的编写方式不是返回任何数据,而是提供一个回调函数来它对这些数据采取行动。我将编辑我的问题以包含 BackendApi.call
的代码
jobs = data['job_list'];
- 不应该是pendingJobs =
吗?以上是关于调用 setState 时,我的有状态小部件不会更新其状态的主要内容,如果未能解决你的问题,请参考以下文章
Flutter - setState 不更新内部有状态小部件
Flutter:在构造函数中调用 setState():_SharesListState#6c96a(生命周期状态:已创建,无小部件,未安装)