how to avoid the flutter request server flood - flutter

I am using future builder to load some data from the server side, now I found this component will send reqeust to the server side every time refresh, this is my flutter code looks like:
return FutureBuilder(
future: articleDetailController.initArticle(int.parse(article.id)),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
articleDetailController.article = snapshot.data;
return new ArticleDetail();
} else {
return Center(child: CircularProgressIndicator());
}
});
when render this component, the reqeust will trigger so much times, how to avoid the reqeust flood to make the request send only once when load the article detail? this is the initial article function:
Future<Item> initArticle(int id) async {
Item? articleWithContent = await Repo.fetchArticleDetail(id);
if (articleWithContent != null) {
return articleWithContent;
}
return new Item();
}
I have tried to use AsyncMemoizer runonce in the async package but facing a new problem that it only run once with different article id, I want it changed with the article id. I define AsyncMemoizer the like this:
AsyncMemoizer _memoization = AsyncMemoizer<Item>();
and the get article code like this:
Future<Item> initArticle(int id) async {
return await this._memoization.runOnce(() async {
Item? articleWithContent = await Repo.fetchArticleDetail(id);
if (articleWithContent != null) {
return articleWithContent;
}
return new Item();
});
}
this code only load the server api for once, could not load each article by the article id. I also tried to use a flag to control the render invoke action:
Future<Item> initArticle(int id) async {
if (!run) {
run = true;
Item? articleWithContent = await Repo.fetchArticleDetail(id);
run = false;
if (articleWithContent != null) {
return articleWithContent;
}
}
return Future.value(new Item());
}
but the FutureBuilder seem did not render the article that returned from server.

Finally I am using synchronized package to avoid this problem, this code look like this:
Future<String> initArticle(int id) async {
return await lock.synchronized(() async {
// Only this block can run (once) until done
// https://stackoverflow.com/questions/74194103/how-to-avoid-the-flutter-request-server-flood
Item articleWithContent = await Repo.fetchArticleDetail(id);
article = articleWithContent;
return articleWithContent.id;
});
}
import this package and initial like this:
import 'package:synchronized/synchronized.dart';
var lock = new Lock();
By the way, do not forget to cache the article in the deep level of your request function, it still need to load multiple times, the first time fetch from server, and fetched from cache in the rest of request.

Related

is it possible to make the AsyncMemoizer cached by key

I am using AsyncMemoizer to run once like this in the FutureBuilder:
AsyncMemoizer _memoization = AsyncMemoizer<String>();
Future<String> initArticle(int id) async {
return await this._memoization.runOnce(() async {
Item? articleWithContent = await Repo.fetchArticleDetail(id);
if (articleWithContent != null) {
article = articleWithContent;
return articleWithContent.id;
}
return "0";
});
}
Now the this._memoization.runOnce code block only run once, but what I want to do only run once with the same article id. how to make the AsyncMemoizer run once with the article id? I have read the AsyncMemoizer source code but did not figure out what to do.

How to extract values from onCall firebase function and load them in future builder

i have a onCall cloud function which is returning
resp.status(200).send(JSON.stringify(entities));
In my flutter app, i have created this future to get values from it.
Future<void> dataDriven(String filename) async {
HttpsCallable callable =
FirebaseFunctions.instance.httpsCallable('fruitsType');
final results = await callable;
final datE = results.call(<String, dynamic>{
'filename': 'filename',
});
final dataF = await datE.then((value) => value.data);
print (dataF);
}
It is successfully printing the response which is as per expectation. but my snapshot is always returning null. It is not even reaching hasData stage. Please help.
Response;
[{"name":"banana","type":"fruit","count":0,"color":"yellow"},{{"name":"apple","type":"fruit","count":2,"color":"red"}]
FutureBuilder(
future: dataDriven('fruits.txt'),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: Text('An error has occurred!'),
);
} else {
final data = snapshot.data;
return Text(data.toString());
}
It looks like there are some issues that need to be fixed (See comments in code).
// Set the correct return type (not void because you are returning data)
Future<String> dataDriven(String filename) async {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('fruitsType');
// You can just call the function here with await
final result = await callable.call({
// Remove the quotes on the filename value
'filename': filename,
});
// Don't forget to return the data
return result;
}
I suggest reading up on the documentation about calling cloud functions from a flutter app and basic dart syntax.

When I am using the provider package in Flutter to load data from an API into a list it repeatedly calls the API, how do I fix it?

I am trying to lode data from an api call that retrieves a map, I am able to get the map from the api to display how I want it to, however it repeatedly calls the api meaning the list keeps on refreshing. Even though I have tried setting the listener to false, it works but I have to manually refresh the app for it to work?
Additional Info: Assigning and Retrieving Data
import 'package:http/http.dart' as http;
class Stores with ChangeNotifier {
var s_length;
Future<List<Store>> getStores(String storeCatName) async {
final queryParameters = {
"store_category_name": storeCatName,
};
try {
//TODO this is the issue - must fix.
final uri = Uri.http("url", 'url', queryParameters);
//print(uri);
final response = await http.get(uri);
//print(response.statusCode);
//print(response.body);
if (response.statusCode == 200) {
final List<Store> stores = storeFromJson(response.body);
_stores = stores;
//print(_stores);
print("lenght: ${_stores.length}");
Store store;
for(store in _stores) {
store.products = Products().products(store.storeId);
}
//check if this is correct
notifyListeners();
//return stores;
} else {
print("error1");
return List<Store>();
}
} catch (e) {
print(e.toString());
return List<Store>();
}
//notifyListeners();
print(_stores);
}
List<Store> get favoriteItems {
//return _stores.where((storeItem) => storeItem.isFavorite).toList();
}
bool isNotFull(){
if (_stores.isEmpty){
return true;
} else {
return false;
}
}
int get numberOfStores{
return s_length;
}
List<Store> _stores = [];
List<Store> stores (String storeCatName){
getStores(storeCatName);
//print("cpp; + $s_length");
//notifyListeners();
return _stores;
}
}
final storesProvider = Provider.of<Stores>(
context, listen: false
);
storesProvider.getStores(categoryName);
final providerStoreList = storesProvider.stores(category.storeCategoryName);
Additional Info: Builder for List:
child: ListView.builder(
itemCount: providerStoreList.length,
itemBuilder: (context, index) => ChangeNotifierProvider.value(
value: providerStoreList[index],
child: StoreItem(),
)));
If any additional information is required just let me know. Any help would be greatly appreciated.
Thanks
Use
listen: false;
var ourClient = Provider.of<CartBlock>(context, listen: false);
Setting the listener to false means that your widget won't build again when notifyListeners() is called.
So, that might not be the issue.
The only reason I can think of is calling the API again from the build method,
which might happen if you are using a ListView builder.
So, every time you might be scrolling the ListView your API would call again.

Call async functions in build method flutter

I need to get the text wrote inside a ".txt" file, save it in a variable and give it to a Text, inside a TextField.
The idea is to write the user input in a ".txt" file so he can read what he wrote when needed on the TextField.
All works, when I read the file it takes the right content but when I store it in a variable to use it Text(var_name...) well what I read on the screen is "Instance of 'Future'".
I know this problem comes from a bad handling of async and future but I would like to really understand why this isn't working.
This is my code :
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localBio async {
final path = await _localPath;
print(path);
return File('$path/bio.txt');
}
Future<File> _write(String text, String filename) async {
final file = await _localBio;
// Write the file.
return file.writeAsString(text);
}
Future<String> _read() async {
try {
final file = await _localBio;
String body = await file.readAsString();
// Read the file.
return body;
} catch (e) {
// If encountering an error, return 0.
return "Can't read";
}
}
Future<String>_MyRead() async {
String read_ = await _read();
print(read_);
return read_;
}
Please write a full answer, I tried a lots of video, forums...Don't just tell me to do var str= _MyRead().then((value) => value);
Maybe it can be the answer but please write 2 more lines because I want to understand why this isn't working.
I took the code from dev official documentation.
You are using an asynchronous value in a rendering process (the build function of a stateful/stateless widget) which is synchronous. You can't just put a Future of String into a place of a String. It won't work. Why? Because it is of a different type, and you need special methods to convert a variable from one type to another.
In this case, you might want to transform this Future into a String asynchronously during the build process. You can use a FutureBuilder for that.
return FutureBuilder<String>(
future: _myRead,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else {
return Text('awaiting the future');
}
},
);
If you don't transform this Future into a String to be rendered, it will just be an Instance of Future.
you should use a FutureBuilder if you wanna render something that takes time (asynchronous)
FutureBuilder(
future:_myRead,
builder: (ctx,snapshot) {
if(snapshot.connectionState == connectionState.waiting) {
return // your waiting Widget Ex: CircularLoadingIndicator();
} else if (snapshot.hasData) {
return Text(snapshot.data.toString()); // toString() is just to be safe
} else { //probably an error occured
return Text('Something went wrong ...');
}

Building widget in Flutter when response statusCode on API call is >400

So I'm trying to call the REST API for the login here. This is in my api_services.dart where I am calling all the APIs for the application.
api_services.dart
Future<User> loginUser(String email, String password)
async {
final response = await http.post(serverOauthUrl+'/token',
headers: {
HttpHeaders.AUTHORIZATION: "xxxx"
},
body: {
"email":"$email",
"password":"$password",
}
);
print(response.statusCode);
final responseJson = json.decode(response.body);
return new User.fromJson(responseJson);
}
And there are two ways I can call this loginUser() method in my UI files and get the response. One that uses the then() method and the other uses FutureBuilder. However, in none of the method, can I get the status code. My use case is that when the status code is >400, I will build a widget that shows the error message.
login_screen.dart
then() method code:
_callLoginAPI(String email, String password){
loginUser(userName, password, "password").then((response) {
response.data.token;
// want my status code here as well along with response data
}
else
{
//todo show something on error
}
}, onError: (error) {
debugPrint(error.toString());
});
}
Or using FutureBuilder :
return new FutureBuilder<User>(
future: loginUser(email, password),
builder: (context, snapshot) {
if (snapshot.hasData) {
print(snapshot.data.token);
} else if (snapshot.hasError) {
print(snapshot.error);
return new Text("${snapshot.error}");
}
return new CircularProgressIndicator();
},
);
What I want to do is something like this
if(response.statusCode > 400)
return new Text("Error"):</code>
Thanks to #Thomas, this issue is resolved. Was an easy solution actually.
Adding the changes in the code for other beginners to follow :
api_services.dart
Future<http.Response> loginUser(String email, String password) async {
final response = await http.post(serverOauthUrl+
'/token',
headers: {
HttpHeaders.AUTHORIZATION: "Basic xxx"
},
body: {
"email":"$email",
"password":"$password",
}
);
return response;
}
So instead of the User, I'm returning the http.Response object and now I can retrieve all the required info from the UI files.
Like this:
final responseJson = json.decode(response.body);
User user = User.fromJson(responseJson);
print(user.userName);
Hope it helps somebody
Why aren't you return an Api Result object instead of a user that contains the error code and the user?
Then you can build different widgets on your FutureBuilder depending on the status code.