Flutter: Variable updated in setState, not updating inside the future function in Future Builder. I am trying to generate api url dynamically

huangapple go评论61阅读模式
英文:

Flutter: Variable updated in setState, not updating inside the future function in Future Builder. I am trying to generate api url dynamically

问题

以下是您提供的文本的中文翻译部分:

我在Stackoverflow和互联网上搜索了很多,但一直没有找到答案。
我的目标是:我试图使用单选按钮和文本字段动态生成API端点。用户将点击单选按钮,该按钮将生成API端点,直到筛选条件。例如,http://localhost:3000/student/name/ 或 http://localhost:3000/student/id/。
现在,之后,我希望这个端点的最后部分由文本字段中的值填充,该值将指向精确的搜索参数。
为此,我正在设置状态以通过将文本字段值与选择单选按钮后生成的端点连接来更新searchAPICall变量。
现在,此变量将传递给将来的future builder函数,并使用来自fetch http.get函数的结果填充列表。
我已经在这个问题上花了最后3天,但还没有能够做到。请帮帮我。这是我在stackoverflow上的第一个问题,所以请理解。如果我走错了路或不必要地提问,请指导我。但我已经搜索了很多来找到答案。

如您从下面的代码中看到的,我以多种方式确认了字符串url正在连接,但是即使在设置状态之后,该函数也没有更新为变量的最新值。
根据我的理解,任何依赖于使用有状态小部件中的setState更新的变量的变量的东西都会自动更新为变量的最新值。
我可能会尝试使用provider包来解决这个问题,但我需要了解我做错了什么。我对setState的理解是否错误?在类似情况下,当我需要动态地更新/修改/操作函数的参数时,应该怎么做,使用用户输入或多个用户输入。
请帮助我弄清楚这个问题的底层原理。如果可能的话,请帮助我解决这个问题,而不使用任何包。谢谢!
请在下面找到我的代码。 (这是一个桌面应用程序,不需要模拟器)。
(在原问题后进行编辑):我需要的概念澄清是,一个状态变量(其状态我正在更改)是否可以作为参数传递给返回future的函数?此外,传递给Future函数的是更新后的值还是先前的值。根据下面的示例,我已经测试了一千次,只传递了先前的值(选择单选按钮之前的值,而不是追加文本字段之后的值)。
我只需要了解底层发生了什么,以便我可以相应地行事,并找到一种传递更新值的方法。

英文:

I have searched on Stackoverflow and a lot on the internet, but haven't been able to find an answer to this.
My goal: I am trying to generate an API endpoint dynamically using, radio buttons and TextField. User would click on radio button that would generate the API endpoint upto the filtering criteria. eg. http://localhost:3000/student/name/ or http://localhost:3000/student/id/.
Now, after this, I want the last part of this endpoint to be filled by the value from TextField, which would pinpoint to the exact search parameter.
For that, I am setting state to update the searchAPICall variable by concatenating the TextField value with the endpoint generated after selecting Radio button.
Now, this variable will be passed to the future: function of the future builder and populate the list with the result from the fetch http.get function.
I have been going over this for last 3 days and haven't been able to do so. Please help me. This is my first question on stackoverflow, so please bear with me. Please guide me, if I am on the wrong path or unnecessarily asking question. But I have searched a lot to find answer to this.

As you can see from the below code, I have confirmed in many ways that the string url is concatenating but somehow even after setting state, the function is not updated with the latest value of the variable.
As per my understanding anything that depends on the variable, whose state is updated using setState in a stateful widget is updated automatically with the latest value of the variable.
I may try to solve this with the provider package, but I need to understand what am I doing wrong. What concept am I getting wrong. Is my understanding of setState wrong? What would I do in similar case when I need to update/modify/manipulate a parameter of a function dynamically, using user input or multiple user inputs.
Please help me get to the bottom of this. And please, if you can, help me solve this without using any packages. Thanks!
Please find my code below. (This is intended as a desktop app, no need for emulator).
(Editing after original question): Concept clarification that I need is, can a state variable(whose state I am changing be passed as argument to a function that returns a future? Moreover, what value is being passed to the Future function, the updated value or previous value. As per the below example, I have tested it a thousand times, and only the previous value(value upto selecting radio button is passed and not the value after appending the textfield).
I only need to understand what is happening under the hood, so I can act accordingly and find a way to pass the updated value.

import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
// import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import '../../models/PatientDemo.dart';
import '../patientListWidgets/patientSearchList.dart';
import '../patientListWidgets/patientSearchListCopy.dart';
import '../main_widgets/patientListViewBuilder.dart';
class PatientSearchDialog extends StatefulWidget {
const PatientSearchDialog({super.key});
@override
State<PatientSearchDialog> createState() => _PatientSearchDialogState();
}
enum SearchOption {
firstName,
lastName,
id,
}
class _PatientSearchDialogState extends State<PatientSearchDialog> {
String searchAPIcall = '';
SearchOption? selectedRadio = SearchOption.firstName;
final searchTextFieldController = TextEditingController();
bool isSearching = false;
Future<List<PatientDemo>> fetchAllPatients(url) async {
// print("fetchAllPatients in PatientDemoScreen called.");
// print("fetchAllPatients received this end point:" + url);
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final List result = jsonDecode(response.body);
// print(result);
return PatientDemo.fromJsonList(result);
} else {
// throw Exception('Failed to load data');
throw Error();
}
}
@override
void initState() {
// TODO: implement initState
}
@override
void dispose() {
// TODO: implement dispose
searchTextFieldController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
switch (selectedRadio) {
case SearchOption.firstName:
setState(() {
searchAPIcall = "http://localhost:3000/patients/name/";
});
break;
case SearchOption.id:
setState(() {
searchAPIcall = "http://localhost:3000/patients/id/";
});
break;
default:
searchAPIcall = "http://localhost:3000/patients/name/";
break;
}
return Dialog(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 19.0, vertical: 9.0),
child: Column(
children: [
TextField(
controller: searchTextFieldController,
decoration: InputDecoration(
labelText: "FirstName / LastName / PatientNo.",
),
onSubmitted: (value) {
// print(value);
// setState(() {
//   searchAPIcall = searchAPIcall + value;
//   fetchAllPatients(searchAPIcall);
//   isSearching = true;
// });
// print('From onSubmitted of TextField: $searchAPIcall');
},
onEditingComplete: () {
// setState(() {
//   searchAPIcall =
//       searchAPIcall + searchTextFieldController.text;
//   isSearching = true;
// });
// print('From onEditingComplete of TextField: $searchAPIcall');
// print(searchTextFieldController.text);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
//radiobuttons
radioButtonListTile("Firstname", SearchOption.firstName),
radioButtonListTile("Lastname", SearchOption.lastName),
radioButtonListTile("Patient No.", SearchOption.id),
],
),
isSearching
? FutureBuilder(
// future: fetchAllPatients('http://localhost:3000/patient/name/John'), // this works, but that's because this is not dynamically generated
future: fetchAllPatients(
searchAPIcall), // unable to send the dynamically generated url to this function
builder:
(context, AsyncSnapshot<List<PatientDemo>> snapshot) {
print(snapshot);
print(snapshot.data);
print(snapshot.connectionState);
if (snapshot.hasData) {
//List view builder import
return PatientListViewBuilder(data: snapshot.data);
} else if (snapshot.hasError) {
return Text(
'${snapshot.error}',
);
} else if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Text("Error in retreiving data");
},
)
: Padding(
padding: const EdgeInsets.all(19.0),
child: Text(
"Enter data in the textfield and choose the criteria for search",
style: TextStyle(fontSize: 19.0, color: Colors.blue),
),
),
Container(
margin: const EdgeInsets.only(
top: 3,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
ElevatedButton(
onPressed: () {
setState(() {
searchAPIcall =
searchAPIcall + searchTextFieldController.text;
print("From SEARCH BUTTON $searchAPIcall");
// fetchAllPatients(searchAPIcall); // when I do this, the dynamically generated url does get passed to this function
isSearching = true;
});
},
child: const Text(
"SEARCH",
),
),
ElevatedButton(
onPressed: () async {
Logger().d("OPEN pressed");
//TODO: Populate the previous widget with the values received from the API call by joining radiobutton and textfield.
// send the API end point call to the next function which will fetch the data from the end point and populate the fields
},
child: const Text(
"OPEN",
),
),
ElevatedButton(
onPressed: () {
Logger().d("CANCEL pressed");
Navigator.pop(context);
},
child: const Text(
"CANCEL",
),
),
],
),
),
],
),
),
);
}
Widget radioButtonListTile(String label, SearchOption searchParam) {
return Flexible(
child: ListTile(
title: Text(label),
leading: Radio<SearchOption>(
value: searchParam,
groupValue: selectedRadio,
onChanged: (SearchOption? value) {
setState(() {
selectedRadio = value;
});
},
),
),
);
}
}

答案1

得分: 1

感谢用户pskink在评论部分的回答。

在这种情况下,SetState 不起作用。这需要使用 StreamBuilder。StreamBuilder 接受输入流并在 builder 字段中更新它。SetState 有其用例,StreamBuilder 也有其用例。

最有可能的问题是我试图将值传递给一个 future 函数。从理论上讲,这是有道理的,但结果是 SetState 有其限制。在 setState 更新变量值之前,未来函数已经运行,而一旦未来函数使用之前的值(在 setState 更新变量值之前不完整的值)运行,该函数就不能再次执行。因为这不是 setState 的工作方式。

而在使用 Stream Builder 的情况下,它不断将输入流馈送到函数中(无论是未来函数还是常规函数),并再次执行函数。因此,即使未来函数以旧的不完整值作为参数,StreamBuilder 也立即使用未来函数之前未收到的新值再次执行它。

即使我的理解与 setState/StreamBuilder/Flutter 在这种情况下的实际内部工作方式大相径庭,结论是,在这种情况下,StreamBuilder 是一个不错的选择。

此外,我意识到与使用 setState 相比,StreamBuilder 是一个更强大的更新状态的工具。
没有办法将问题标记为已解决,所以我将在这个答案的开头和结尾放上大写字母以通知查看此页面的任何人。

英文:

Thanks to user pskink for his answer in comments section.

SetState would not work in this case. This required StreamBuilder. StreamBuilder takes the input stream and updates it inside the widget in builder field. setState has it's use case, and StreamBuilder has its own use case.

Most probably, the issue was that I was trying to pass the value to a future function. Theoretically, it made sense, but turns out setState has it's limitations. The future function was running before the value that was being set in setState reached the future Function. And once the future Function ran with the previous value(the incomplete value before setState could update the variable's value), the function could not be executed again. Because this is not how setState works.

While in case of Stream Builder, it continuously feeds in the input stream to the function(doesn't matter if it's a future function or usual function), and function executes again. So, even if the future function received the old incomplete value as it's argument, the StreamBuilder made it execute immediately again with the new value that the future Function did not receive before.

Even if my understanding is way off the actual inner working of setState/StreamBuilder/Flutter in this case. The conclusion is that in such a case, StreamBuilder is the option to go with.

Moreover, I realized that StreamBuilder is a much strong tool to update state compared to using setState.
There is no way to mark a question as resolved so I am going to put it in CAPS at the start and end of this answer to notify anyone who views this page.

huangapple
  • 本文由 发表于 2023年2月27日 01:58:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75573973.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定