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

Issue

Background

Inside the state object of my stateful widget, I have the following code.

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

Details about the above code

👉 List<String> pendingJobs = []; is the variable that I am expecting to have a state change done.

👉 The function defined right below the above variable called updateListWithResponseData takes a parameter of type List. It is also responsible for calling another utility function called BackendApi.call().

👉 I am calling the udateListWithResponseData() inside the initState and for the parameter of type List it takes, I am giving the pendingJobs variable that I have defined. (Since I am calling setState from within the updateListWithResponseData() function, I am expecting the the state of pendingJobs to change when updateListWithResponseData is called.)

👉 However, the state change I am expecting in the above point is not taking place.

👉 BackendApi.call is responsible for fetching data from a given url and it takes two callback functions for onSuccess and onFailure which are responsible for performing necessary actions depending on the data fetching is a success or not.

An important note

Removing the List jobs parameter from the updateListWithResponseData and directly referring to the pendingJobs variable is not a solution for me since I am expecting to extract the function called updateListWithResponseData to a separate dart file and call it from different widgets. So I have to have that List jobs parameter in the function.


I tried to debug this issue for some time now and could not find a solution. It would be really helpful if someone can point out why the state of pendingJobs is not changing and how to actually make it change. (Thanks.)


**Edit**

Since a lot of comments below seem to revolve around the BackendApi.call() function and since I had not included the code for that function in my original post I have edited this post to include the code for that.

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

Solution

The reason behind this behaviour is that dart parameters are passed by value. (i.e. a copy of the variable is passed with the variable data)

So here you are passing a copy of the values in pendingJobs which happens to be a reference to the list.

@override
  void initState() {
    updateListWithResponseData(pendingJobs);    <------- This is where the function in which I call the setState is executed.
    super.initState();
  }

and now updateListWithResponseData has its own variable jobs that holds a copy of pendingJobs reference

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!');
        });
  }

so what this jobs = data['job_list']; does is assaigning the local variable (to updateListWithResponseData) jobs value, this change will not be reflected on pendingJobs as you are only updating the copy within updateListWithResponseData.

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.

Answered By – Abdulrahman Ali

Answer Checked By – Candace Johnson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *