调用 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&lt;String&gt; pendingJobs = []; 是我期望完成状态更改的变量。

????在上述变量updateListWithResponseData 正下方定义的函数采用List 类型的参数。它还负责调用另一个名为BackendApi.call() 的实用程序函数。

????我在initState 中调用udateListWithResponseData(),对于List 类型的参数,我给出了我定义的pendingJobs 变量。 (由于我是从updateListWithResponseData() 函数中调用setState,我希望pendingJobs 的状态在调用updateListWithResponseData 时会发生变化。)

???? 但是,我在上面所期望的状态变化并没有发生。

???? BackendApi.call 负责从给定的 url 中获取数据,它为 onSuccessonFailure 提供两个回调函数,它们负责根据数据获取是否成功执行必要的操作。

重要说明

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&lt;void&gt; 之后它仍然存在。我认为这是因为BackendApi.call() 没有返回Future。因此,我认为等待它会发出警告.... 您能否包含 BackendApi.call,我认为它应该返回 Future&lt;void&gt; 而不是 void,因为我们在 HTTP 上请求并且它的响应将是未来的? 我试过这个。但是状态仍然没有改变......我认为制作BackendApi.callFuture&lt;void&gt;是没有必要的,因为该api util的编写方式不是返回任何数据,而是提供一个回调函数来它对这些数据采取行动。我将编辑我的问题以包含 BackendApi.call 的代码 jobs = data['job_list']; - 不应该是pendingJobs = 吗?

以上是关于调用 setState 时,我的有状态小部件不会更新其状态的主要内容,如果未能解决你的问题,请参考以下文章

在颤振中从父小部件调用 setState 不会更新状态

从无状态小部件调用有状态小部件的 setState

Flutter - 我的动画只播放一次

Flutter - setState 不更新内部有状态小部件

冻结的 BLoC - 产量不会向小部件发出状态更改

Flutter:在构造函数中调用 setState():_SharesListState#6c96a(生命周期状态:已创建,无小部件,未安装)