How do I load and parse a Json in isolate? - flutter

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);

Related

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 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.

Flutter where to put http.get

I am making lecture room reservation system.
class SearchView2 extends StatefulWidget {
#override
_SearchViewState2 createState() => _SearchViewState2();
}
class _SearchViewState2 extends State<SearchView2> {
String building = Get.arguments;
List data = [];
String roomID = "";
int reserved = 0;
int using = 0;
Future<String> getData() async {
http.Response res = await http.get(Uri.parse(
"https://gcse.doky.space/api/schedule/classrooms?bd=$building"));
http.Response res2 = await http.get(Uri.parse(
"https://gcse.doky.space/api/reservation/currtotal?bd=$building&crn=$roomID"));
reserved = jsonDecode(res2.body)["reserved"];
using = jsonDecode(res2.body)["using"];
this.setState(() {
data = jsonDecode(res.body)["result"];
});
return "success";
}
#override
void initState() {
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('강의실 선택')),
body: new ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
roomID = data[index];
return new Card(
child: ListTile(
onTap: () async {}, title: Text(data[index] + " " + reserved)),
);
},
),
);
}
}
I want to get 'using' and 'reserved' data and print them in the list view.
But roomID is in Listview
I want data[index] as roomID but with my code roomID will be null, so it won't print the result.
Where should I move http.Response res2? (not res)
Or is there other way to get using and reserved data in the listview?
First of all, you have a single building and multiple rooms in that building. So, fetching a building data along with the data of all it's rooms together will take too much time.
Instead, you can break it into two parts.
For fetching Building data,
Future<List<String>> getData() async {
http.Response res = await http.get(Uri.parse("https://gcse.doky.space/api/schedule/classrooms?bd=$building"));
return (jsonDecode(res.body)["result"] as List)
.map<String>((e) => e.toString())
.toList();
}
Then, for fetching each room data, Here you have to pass roomID.
Future<Map<String, dynamic>> getRoomData(String roomID) async {
http.Response res2 = await http.get(Uri.parse("https://gcse.doky.space/api/reservation/currtotal?bd=$building&crn=$roomID"));
return {
'reserved': jsonDecode(res2.body)["success"]["reserved"],
'using': jsonDecode(res2.body)["success"]["using"],
};
}
Now, you can use FutureBuilder widget to build something that depends on fetching data asynchronously.
You also don't need a StatefulWidget since you are using FutureBuilder and can remove all unnecessary local variables you have defined.
Here is the full working code. PasteBin Working Code.
Just replace your entire SearchView2 code with the code in the link.
This is the output.

A value of type "Future<Null>" can't be assigned to a variable of type "Data"

I'm trying figured out how to work with future for fetching data over internet. I was trying to write a simple code where I convert Future into "average" data but it doesn't work and I can't get why that's happend.
Here's my code
class Fetch {
Data getData () {
Data data;
data = fetch().then((value) {data = value;}); //here's I'm getting error
}
Future<Data> fetch() async {
int number = await Future.delayed(Duration(seconds: 1), () => 3);
return(Data.fromIt(number));
}
}
class Data{
int date;
Data({this.date});
factory Data.fromIt(int num) {
return Data(
date: num,
);
}
}
After I corrected this part of code error has gone but now getData() returns null instead of value:
Data getData () {
Data data;
fetch().then((value) {data = value;});
return data; //null
}
Thanks
You can make getData an async function, and use the await keyword. It is much easier to understand if you are new to async programming and Futures
Future<Data> getData () async {
final data = await fetch();
// Whatever you want to do with data
return data; // Dont forget to return it back to the caller
}
The data is defined as a Data object, while fetch() returns a Future<Data>, causing the error.
Data data;
data = fetch().then((value) {
data = value;
});
You can not transform a Future object to a synchronous object without awaiting it. You can do this at the UI to get the Future value:
FutureBuilder<Data>(
future: Fetch().fetch(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
Data data = snapshot.data;
return Text(data);
},
)

How can i use the result from the first API call as input for the second API call?

I have to make multiple API calls in order to get the actual data. I have written the below code to make the first API call. It works but I have to use the return value (let'say it returns access token) from the first call, and use this access token as part of the header on the second API call. How can I achieve that?
class Service {
final String url;
Map<String, String> header = new Map();
Map<String, String> body = new Map();
Service(this.url, this.header, this.body);
Future<Data> postCall() async {
final response = await http.post(url, headers: header, body: body);
return Data.fromJson(json.decode(response.body));
}
}
class MyApp extends StatelessWidget {
Service service;
Service serviceTwo;
....
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: FutureBuilder<Data>(
future: service.postCall,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.accessToken);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);}}
There are many ways of achieving that, the simplest one is just using await on your method to append the future calls.
So your method postCall() would be something like this:
Future<Data> postCall() async {
// The first call, suppose you'll get the token
final responseToken = await http.post(url, headers: header, body: body);
// Decode it as you wish
final token = json.decode(responseToken.body);
// The second call to get data with the token
final response = await http.get(
url,
headers: {authorization: "Bearer $token"},
);
// Decode your data and return
return Data.fromJson(json.decode(response.body));
}
If it is a token you'll use many times, I recommend you to store it in flutter_secure_storage and use it as you wish.