Reputation: 151
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();
}
👉 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.)
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;
}
}
}
Upvotes: 1
Views: 1705
Reputation: 46
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.
Upvotes: 1
Reputation: 67
First thing: if you want to do an asynchronous task which will change state when resumed on first build, always do that in a WidgetsBinding.instance.addPostFrameCallback. Second thing: you don't have to use state variable as parameter for state method.
Try this:
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.
}
Upvotes: 0
Reputation: 63559
Try:
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!');
});
}
And on initState
:
updateListWithResponseData(pendingJobs);
Here is a demo widget of that workflow
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"));
}
}
Upvotes: 0