当调用 setState 时,我的有状态小部件不会更新它的状态

My stateful widget does not update it's state when setState is called

背景

在我的有状态小部件的状态对象中,我有以下代码。

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 中获取数据,它需要两个回调函数 onSuccessonFailure 负责执行必要的操作,具体取决于取数据成功与否

重要提示

updateListWithResponseData中去掉List jobs参数直接引用pendingJobs变量是不是 对我来说是一个解决方案,因为我希望将名为 updateListWithResponseData 的函数提取到一个单独的 dart 文件中,并从不同的小部件调用它。 所以我必须在函数中包含 List jobs 参数。


我尝试调试这个问题有一段时间了,但找不到解决方案。如果有人能指出为什么 pendingJobs 的状态没有改变以及如何真正改变它,那将非常有帮助。 (谢谢。)


**编辑**

由于下面的很多评论似乎都围绕 BackendApi.call() 函数,并且由于我没有在我的原始 post 中包含该函数的代码,所以我编辑了这个 post包括代码。

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;
    }
  }
}

尝试:

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"));
  }
}

第一件事:如果你想做一个在第一次构建时恢复时会改变状态的异步任务,总是在 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.
  }

此行为背后的原因是 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']; 所做的是分配局部变量(给 updateListWithResponseDatajobs 值,这个变化不会反映在 pendingJobs 上,因为你只是在 updateListWithResponseData.

内更新副本

要解决此问题,请删除分配 jobs = data['job_list']; 并将其替换为 jobs.addAll(data['job_list']); 这样 pendingJobs 值也会更新。