Call async functions in build method flutter - 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 ...');
}

Related

how to avoid the flutter request server flood

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.

Extract String From Future<String> In Flutter

I'm using flutter, and I'm loading in a locally stored JSON file like so:
Future<String> loadJson(String file) async {
final jsonData = await rootBundle.loadString("path/to/$file.json");
return jsonData;
}
The problem is that this returns a Future<String> and I'm unable to extract the actual JSON data (as a String) from it.
I call loadJson in the Widget build method like so:
#override
Widget build(BuildContext context) {
final data = ModalRoute.of(context)!.settings.arguments as Map;
final file = data["file"];
String jsonData = loadJson(file); // The issue is here
return Scaffold (/* -- Snip -- */);
}
How would I go about doing this? Any help is appreciated.
loadJson is Future and you need to await for its result:
String jsonData = await loadJson(file);
you also can't run Future function inside build method, you need to use FutureBuilder:
return Scaffold (
body: FutureBuilder<String>(
future: loadJson(file),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
String jsonData = snapshot.data ?? "";
return /* -- Snip -- */;
},
}
}
},
),
);
You are getting data but not decoding it. You need to decode for using the loaded data.
Future<String> loadJson(String file) async {
final jsonData = await rootBundle.loadString("path/to/$file.json");
final data = await jsonDecode(jsonData)
return data;
}
Also, please don't forget to import dart convert library to use jsonDecode.
import 'dart:convert';

How return bool from write on file

Am using this for write file on phone
Future<File> writeData(data) async {
final file = await _localFile;
return file.writeAsString(data);
}
how can i know if it write successfully on file ? like is there a way to return a bool value when i write on file to know it write successfully ?
Failures in file writing will be errors or exceptions. You could catch them all and return false in that case and otherwise true.
Future<bool> writeData(data) async {
try {
final file = await _localFile;
file.writeAsString(data);
return true;
catch (_) {
return false;
}
}
Personal opinion ahead:
It would be wiser to handle those errors properly instead of returning a boolean though.

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.

How to resolve future in dart?

I need to read and write files on Flutter.
Write works, but read not or I think it doesn't because the terminal output is flutter: Instance of 'Future<String>'.
What does it means?
This is the code :
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/hello.txt');
}
Future<File> writeHello() async {
final file = await _localFile;
// Write the file.
return file.writeAsString('HelloWorld');
}
Future<String> readHello() async {
try {
final file = await _localFile;
// Read the file.
return await file.readAsString();
} catch (e) {
// If encountering an error, return 0.
return "Can't read";
}
}
.
.
.
writeHello();
print(readHello());
Future<String> is of type Future hence you need to resolve the future, You can either await before printing or use .then() to resolve the Future.
Using await
String data = await readHello();
print(data);
Using .then()
readHello().then((data){ //resolve the future and then print data
print(data);
});
Note: There is no need to add extra "await" here on line 2 as you already are awaiting at line 1:
Future<String> readHello() async {
try {
final file = await _localFile; //Line 1
// Read the file.
return await file.readAsString(); //Line 2
} catch (e) {
// If encountering an error, return 0.
return "Can't read";
}
}
Now I got it, I understood what you said me thank you!
I created a new function that mix write and read.
The problem is that I called async functions in my program body where I can't use await , I should call them in other async functions to handle them in the right way.
I solved with this :
void _RWHello(String text) async {
writeHello();
print(await readHello());
}
.
.
.
_RWHello("HelloWorld");