Extract String From Future<String> In Flutter - 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';

Related

FirebaseStorage - how to get all images in a folder

I'm trying to list all images in a created folder in FirebaseStorage.
Future getImages(String folderName) async {
final docRef = FirebaseStorage.instance.ref().child(folderName);
List imageRef = [];
docRef.listAll().then((result) async {
imageRef = result.items;
});
return imageRef;
}
FutureBuilder:
FutureBuilder(
future: getImages(images.first),
builder: (context, AsyncSnapshot snapshot) {
if(snapshot.hasData){
List list = snapshot.data;
return Image.network(list.first);
} else {
return const Center(child: CircularProgressIndicator(),);
}
}),
I can not return anything from a then() function neither can I use its value outside its body!
I thought about returning a future object and then use it inside my FutureBuilder but again I need to return a widget and I can't do that inside a then() function
Try to use await in getImages to get the list result and return the items like this:
Future<List<Reference>> getImages(String folderName) async {
final docRef = FirebaseStorage.instance.ref().child(folderName);
final listResult = await docRef.listAll();
return Future.value(listResult.items);
}
But if you need the download url of the images, you have to add further code since the above will return a List of Reference, and the getDownloadURL method of Reference is also async. You could try this if you need a list of urls:
Future<List<String>> getImages(String folderName) async {
final docRef = FirebaseStorage.instance.ref().child(folderName);
final listResult = await docRef.listAll();
final urls = <String>[];
for (var item in listResult.items) {
urls.add(await item.getDownloadURL());
}
return Future.value(urls);
}

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.

getting future string and saving state in flutter

I am trying to get the string value of a future, and saving state in flutter. user chooses the endTime and it should display on the UI untill it ends. however, I am getting the following error:
type 'String' is not a subtype of type 'Future<String>' in type cast
the method:
final Future<SharedPreferences> _prefs =
SharedPreferences.getInstance();
Future<String> _textLine = '' as Future<String>;
Future<String> fastTrue() async {
final SharedPreferences prefs = await _prefs;
String formattedDate = DateFormat('yyyy-MM-dd,
hh:mma').format(endTime);
final textLine = (prefs.getString('formattedDate') ??
Languages.of(context)!.setYourFastTime) as Future<String>;
setState(() {
_textLine = prefs.setString('formattedDate',
Languages.of(context)!.endTimeIs
+'\n$formattedDate').then((bool success) {
return textLine;
});
});
return textLine;
}
in initState():
#override
void initState() {
super.initState();
_textLine = _prefs.then((SharedPreferences prefs) {
return prefs.getString('formattedDate') ??
Languages.of(context)!.setEndTime +'\n'+DateFormat('yyyy-MM-dd,
hh:mma').format(DateTime.now());
});
then in my widget build():
Padding(padding: const EdgeInsets.only(top: 170),
child: FutureBuilder<String>(
future: _textLine,
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text(
Languages.of(context)!.endTimeIs +
"\n${snapshot.data}"
);
}
}
})),
help me, pls, tried using hive, but was not able to get to save the state of the widget. Thanks!
This code throws the error because you try to cast a String to a Future<String>>, although it is a String.
Future<String> _textLine = '' as Future<String>;
If you want to declare a Future with a value, you can use the value method.
Future<String> _textLine = Future.value('');

How do I load and parse a Json in isolate?

I'm trying to load my json and parse it in a new isolate. If I understood correctly this allows better performance of the application.
I charge my json and parse it after my future builder.
I would like to parse it before in an isolate and return them all but I can't display my json.
Is this the right method or it's useless ?
Future<Map<String, dynamic>> loadJson() async {
/*
* I load my Json without other isolate and I parse it in the future builder
*/
final myJson = await rootBundle.loadString('assets/jsons/myJson.json');
return {
'myJson' : myJson
};
}
Future<Map<String, dynamic>> loadJsonParsed() async {
/*
* I load my Json and parse it in other isolate
*/
final myJson = await rootBundle.loadString('assets/jsons/myJson.json');
var myJsonParsed = compute(_parseJson, myJson);
return {
'myJson' : myJsonParsed
};
}
Map<String, dynamic>_parseJson(String responseBody) {
final parsed = jsonDecode(responseBody);
return parsed;
}
// Some code ...
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: Future.wait([loadJson(), loadJsonParsed()]),
builder: (context, snapshot) {
if(snapshot.hasError) print(snapshot.error);
if (!snapshot.hasData) {
return LinearProgressIndicator();
}
print('BEGIN ================================');
var myJson = json.decode(snapshot.data[0]['myJson']);
print(myJson); // I see my json !
print(myJson.runtimeType); // _InternalLinkedHashMap<String, dynamic>
print('SEPARATION ================================');
var myJsonParsed = snapshot.data[1]['myJson'];
print(myJsonParsed); // Instance of 'Future<Map<String,dynamic>>' How to do ?
print(myJsonParsed.runtimeType); // Future<Map<String,dynamic>>
print('END ================================');
If the json is small, probably it's not worth to move the parsing to a different isolate as this operation also has a cost. Small / big is something subjetive but using common sense we could likely apply the correct approach.
If the json is big, definitively you're doing the correct thing as otherwise dart would execute the parsing on the main thread, what could cause a visible jank.
In relation to why compute is working wrong for you, this function returns a Future so instead of doing this
var myJsonParsed = compute(_parseJson, myJson);
You need to await the call to get the result, otherwise you'll be storing a future here 'myJson' : myJsonParsed and not the json map
var myJsonParsed = await compute(_parseJson, myJson);

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