Getting data from json by key, not index, with flutter - json

I have a json table with this structure;
{id: 1, deviceid: 1, devicenumber: 11, number1: 1, number2: 2, number3: 3, number4: 0, number5: 5, date: 0000-00-00 00:00:00}
With this code, I can pull data from the json table according to the index. but what I want is to pull all the json data and search in all keys.
because i will not always know the index number but deviceid key number is always available. That's why it's to match the captured data with the data I have and get other correct information.

I will assume you are going to work with FutureBuilder so my answer is based around that but you should get the basics out of it
I would create a class for your json data first
Class ClassName {
final String? testText
ClassName({this.testText});
factory ClassName.fromJson(Map<String, dynamic> json) {
return ClassName(testText: json['test_text']);
}
}
Then fetch the url and parse it on your main widget, that fetch should look something like this:
Future<ClassName>? futureData;
Future<ClassName> fetchData() async {
final response = await http.get(Uri.parse(
url));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return ClassName.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Error');
}
}
#override
void initState() {
futureData = fetchData();
super.initState();
}}
Later on when using FutureBuilder you can use this data as a future
FutureBuilder<ClassName>(
future: futureData,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Container(
child: Text(snapshot.data!.testText),)}},

Related

How do you make a http post request from api having params and body in flutter?

I am a newbie in fetching data from APIs
I have an api endpoint containing two query params and also having a body of string and int.
I want to make an already existing post to be featured on the home page with time limit.
in my repo I have written this
class FeaturePost {
Future featurePost(int duration, String period) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
var token = preferences.getString('token').toString();
final queryParameters = {
'postpId': 'postId',
'id': 'id',
};
http.post(Uri.dataFromString("https/url.com/api/v1/post?" parameters: queryParameters),
headers: {
'Content-Type': 'application/json',
'x-access-token': token,
},
body: {
"duration": duration,
"period": period
});
here's my button
GestureDetector(
onTap: () async {
setState(() {
period =
"${dropdownValue}";
});
if (_postKey
.currentState!
.validate()) {
var create =
PostModel(
postId:
widget.postId,
id: widget.userId,
period: period,
duration: int.parse(
durationController
.text),
);
createPost
.newPostDuration(
create,
widget.postId,
);
}
},
child: Center(
child: Text('Make Post Featured',),
),
)
my controller
class FeaturePostController extends GetxController {
final featureAPostRepo = FeaturePost();
Future<dynamic> newFeaturedPost(ostpId, id) async {
try {
final result = await featureAnAdRepo.featureAnAd(postId, id);
Get.back();
await fromFeaturedAds.fetchFeaturedAds();
// Get.snackBar('Success', 'Post Featured Successfully');
print(result);
return result;
} catch (e) {
throw Exception(e);
}
}
}
this is the response i get
Unhandled Exception: Null check operator used on a null value.
Since you're getting Null check operator used on a null value exception, it's probably because you haven't assigned your _postKey to the form that you are trying to validate. Make sure you're wrapping all your fields inside a form and that your key is assigned to the key parameter of that form
I was able to fix it. It was because I wasn't parsing all required params properly. All I had to do was add "postId" and "Id" like this ```featurePost(String postId, String Id, int duration, String period) and some rearrangements and it worked

Can't manage to fetch data from a second JSON file

I'm trying to use a fetched data from a JSON file to make another fetch on a second screen.
Let's say I have a JSON file that was fetched via www.fruits.com/data. Then one of the fruits has an ID of 1. In order to have more information about this fruit, I have to access another JSON file on www.fruits.com/data/1.
I have both of these fetch functions to access JSON and drag said data:
List<Fruitmodel> parseFruit(String responseBody) {
var list = json.decode(responseBody) as List<dynamic>;
List<Fruitmodel> fruits = list.map((model) => Fruitmodel.fromJson(model)).toList();
return fruits;
}
List<FruitDetails> parseDetails(String responseBody) {
var list = json.decode(responseBody) as List<dynamic>;
List<FruitDetails> fruit_details = list.map((model) => FruitDetails.fromJson(model)).toList();
return fruit_details;
}
Future<List<FruitModel>> fetchFruit() async {
final response = await http.get(Uri.parse('https://fruits.com/data'));
if (response.statusCode == 200){
return compute(parseFruits, response.body);
}
else{
throw Exception('Failed to get fruits.');
}
}
Future<List<FruitDetails>> fetchDetails(int? a) async { //"int a" is to get the fruit's ID
String newUrl = 'https://fruits.com/data/' + a.toString();
final response = await http.get(Uri.parse(newUrl));
if (response.statusCode == 200){
return compute(parseDetails, response.body);
}
else{
throw Exception('Failed to get details.');
}
}
On my homepage, I used the first fetch function (FetchFruit), and managed to make a fruit list with the first JSON file by using Future Builder (snapshots), then my next task is to click on a fruit and show its details.
...
onTap:(){
Navigator.push(context,
new MaterialPageRoute(builder: (context)
=> DetailsPage(snapshot.data[index])));
...
So, on my next page, I'm initializing it with data from the fruit I've chosen. Then, I try to make the other fetch function (fetchDetails) by using said fruit's ID contained on the other JSON.
...
body: Center(
child: FutureBuilder(
future: fetchDetails(this.fruit.id), //Using ID to mount the correct URL
...
But... It doesn't work. I did a condition to tell me that if the snapshot has an error, it prints "Data not available" on the screen, and it does that instead of reading the second JSON file. What should I do for the second fetch to be done correctly?
In resume:
1st JSON file -> ID -> used to access 2nd JSON file -> not working
Try using this
String newUrl = 'https://fruits.com/data/$a';
and make sure the value won't be null.

Store a api response data in firebase collections using flutter

So, I have been making a post request to a REST API and I want to store the response data in the firebase cloud store collection.
What I have done so far:
I have created the model class for the response data and have written a function that will make this post-call.
I am not getting any such error but still, neither the response is getting printed in the console nor the data is being uploaded in the firebase.
Also, I have checked with almost all the StackOverflow questions that relate to my kind of problem.
Herewith I am attaching my code snippets:
Function:
//This function is only not getting called I don't know why.
final List<KycDetails> _kyc = [];
Dio dio = Dio();
TextEditingController aadhar = TextEditingController();
Future<List<KycDetails>> postData() async {
const String pathUrl = 'https://jsonplaceholder.typicode.com/posts';
dynamic data = {'title': aadhar.text, 'body': 'Flutter', 'userId': 1};
List<KycDetails> details = [];
var response = await dio.post(pathUrl,
data: data,
options: Options(
headers: {'Content-Type': 'application/json; charset=UTF-8'}));
if (response.statusCode == 200) {
print('ok');
var urjson = jsonDecode(response.data);
for (var jsondata in urjson) {
details.add(KycDetails.fromJson(jsondata));
}
}
return details;
}
Widget where I am calling the function and storing the data in firebase
InkWell(
hoverColor: Colors.red,
onTap: () async {
print('API CALLING');
await postData().then((value) {
setState(() {
_kyc.addAll(value);
});
print(value);
});
Map<String, String> data = {
"aadhar": aadhar.text,
"title": _kyc[0].title,
"userId": _kyc[0].userId.toString(),
};
FirebaseFirestore.instance.collection('kyc').add(data);
},
child: const Text('Submit'),
),
API response data:
{"title": "resume", "body": "Flutter", "userId": 1, "id": 101}
Model Class:
class KycDetails {
KycDetails({
required this.title,
required this.body,
required this.userId,
required this.id,
});
String title;
String body;
int userId;
int id;
factory KycDetails.fromJson(Map<String, dynamic> json) => KycDetails(
title: json["title"],
body: json["body"],
userId: json["userId"],
id: json["id"],
);
Map<String, dynamic> toJson() => {
"title": title,
"body": body,
"userId": userId,
"id": id,
};
}
I hope I have provided you with the necessary information
Am stuck on this problem for quite a few days Would appreciate it if anyone can solve my problem considering my code.
For starters, when you make a post request the success code you're looking for is 201 indicating that a resource has been successfully created.
So nothing in this code block will run.
if (response.statusCode == 200) {
print('ok');
var urjson = jsonDecode(response.data);
for (var jsondata in urjson) {
details.add(KycDetails.fromJson(jsondata));
}
}
response.data doesn't need jsonDecode here. It returns in the form of a map so you can cast it as such.
So this
var urjson = jsonDecode(response.data);
can be this
final urjson = response.data as Map<String, dynamic>;
As for this line
for (var jsondata in urjson) {
details.add(KycDetails.fromJson(jsondata));
}
The response is a single map, not a list. That single map is in the form of your KycDetails model so you don't need to loop through anything.
So you can create your object with your fromJson method.
final kycDetail = KycDetails.fromJson(urjson);
Then you can just do this to add a properly initiated KycDetails object to the list.
details.add(kycDetail);
If all you're trying to do is add a single object to Firebase then none of this in your onTap is necessary. Also trying to access the property at index 0 will not be the most recent addition to the list. You'd need to add the index of the last item in the list.
Map<String, String> data = {
"aadhar": aadhar.text,
"title": _kyc[0].title,
"userId": _kyc[0].userId.toString(),
};
FirebaseFirestore.instance.collection('kyc').add(data);
You can just add to Firebase from your postData function.
if (response.statusCode == 201) {
print('ok');
final urjson = response.data as Map<String, dynamic>;
final kycDetail = KycDetails.fromJson(urjson);
details.add(kycDetail);
FirebaseFirestore.instance.collection('kyc').add(kycDetail.toJson());
}

Flutter web json data not getit

i am new to flutter web but this error is crazy my func to get json data is
#override
Future<List<StoryEntity>> getNewAnimation(int id) async{
print("ali");
return (json.decode(
(await http.Client().get(Uri.parse('https://hekayatname.ir/home/getanimation')))
.body)['list'] as List)
.map(
(e) => StoryEntity(
title: e['fullname'],
imagePath: e['logo_url'],
description: "e['description']",
rating: 1,
galleryImagesPath: [],
id: e['id'],
producer: e['address'],
),
).toList();
}
but i recive nothing in flutter.
in web browser my data is like this just go to this link
https://hekayatname.ir/home/getanimation
i change the code to this
Future<List<StoryEntity>> getNewAnimation(int id) async{
print("1");
final response = await http.Client().get(Uri.parse("https://hekayatname.ir/home/getaudioestory"));
if(response.statusCode == 200){
print("2");
}
else{
print("3");
print(response.statusCode);
}
}
i have nothing to both if and else
if not work,else not work.... need help!!
Visit this link. Copy your json response and paste in the json section. Give your class name and hit Generate Dart.
Now hit copy dart code and add it your project. Then you can use it like inside the success case.
Note if you face any error of Null object. Thats mean your response contains null values and replace this data type with the corresponding data type.
if(response.statusCode == 200){
print('2');
YourJsonResponse obj = YourJsonResponse.fromJson(jsonDecode(response.body));
} else{
print("3");
print(response.statusCode);
}

Flutter: How to use Firebase Storage to get new data if JSON file in Firebase Storage updated

I am currently displaying the data by calling the JSON file from Firebase Storage, but I want that instead of download JSON file every single time to show data => I will check if the JSON file from the Firebase Store has changed:
If it changed => download the new JSON file to Local directory and display it.
Otherwise => display the old JSON file in Local directory (This old JSON file will be downloaded when first time App open)
About JSON File
This is JSON link after I upload JSON to Firebase Storage:
https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f
As far as I know, this link is made up of 2 parts:
First part: https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json
Last part: ?alt=media&token= + 2e3d416-62dc-4137-93a3-59ade95ac38f (it is value of String: "downloadTokens" in First part)
In the First part of the link, there is all information about JSON file, and especially I think that value of String "updated" can be used as a condition for the purpose of downloading files or not.
Ex. "updated": "2020-08-04T14:30:10.920Z",
The value of this String updated will change every time I upload a new JSON file with the same name as the old JSON file but the link download will not change.
Steps
So I want to do the following:
Create file to store String "updated" in Local directory (Ex. "updated": null) and where to store the JSON file after download to Local directory
Open App
Check String "updated" in link First Part:
Case A: if value of String "updated" in First Part != value of String "updated" in Local directory =>
Step 1: download JSON file (by link: First part + ?alt=media&token= + downloadTokens) to Local directory (If the old json file already exists, it will be replaced)
Step 2: overwrite value of String "updated" in Local directory by value of String "updated" in Firebase Storage
Step 3: access JSON file in Local directory to display data
Case B: if value of String "updated" in First Part == value of String "updated" in Local directory => do nothing, just access JSON file in Local directory to display data
I know this is a lot of questions for one post, I'm a newbie with code and if I split it up into a few posts then it is very difficult to combine them for me. So I hope the answer with full code, that would be great. Thanks. This is the main file:
import 'package:ask/model/load_data_model.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class LoadDataPage extends StatefulWidget {
#override
_LoadDataPageState createState() => _LoadDataPageState();
}
class DataServices {
static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';
static Future<List<Data>> getData() async {
try {
final response = await http.get(url);
if (200 == response.statusCode) {
final List<Data> data = dataFromJson(response.body);
return data;
} else {
return List<Data>();
}
} catch (e) {
return List<Data>();
}
}
}
class _LoadDataPageState extends State<LoadDataPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Load Data')),
body: FutureBuilder(
future: DataServices.getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<Widget> children;
List<Data> _data = snapshot.data;
if (snapshot.hasData) {
return ListView.builder(
itemCount: _data.length,
itemBuilder: (context, index) {
return Column(
children: [Text(_data[index].data)],
);
},
);
} else {
children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
}
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
}));
}
}
Another Steps
EdwynZN's answer worked great for me, however, I edit the post to add one more case which I think will make load page ASAP, So please help me again:
After open Page => readFile > compareLastUpdate > _lastUpdateDB & _createFile
Case A: The first time the app opens => readFile: false > _lastUpdateDB & _createFile > readFile again
Case B: Not the first time the app opens:
the data is still loaded immediately from the old JSON, at the same time, run in background: compareLastUpdate:
If update times are the same => do nothing
If update times are different => _lastUpdateDB & _createFile
P/S: With this flow, the second time they open the page then new data will be displayed, right? But I wonder that if using StatefulWidget => after the new JSON file is overwritten to the old JSON file => will the phone screen display new data after that?
I would recommend using shared_preferences to save the last updated date as a String
import 'package:shared_preferences/shared_preferences.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:convert';
/// Move them outside of the class as Top Level functions
List<Data> readFile(File file) {
try{
String data = file.readAsStringSync();
return dataFromJson(data);
} catch(e){
print(e.toString());
return List<Data>(); // or return an empty list, up to you
}
}
// No need of encoder now because response body is already a String
void writeFile(Map<String, dynamic> arg) =>
arg['file']?.writeAsStringSync(arg['data'], flush: true);
class DataServices {
DateTime dateApi;
static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';
static const String urlUpdate = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json';
Future<List<Data>> getData() async {
bool update = await compareLastUpdate;
if(update) { // that means the update times are the same, so retrieving form json file is better than doing http request
final file = await _createFile();
if(await file.exists()) return await compute(readFile, file);
else return null; //or an empty List
// If it doesn't exists (probably first time running the app)
// then retrieve an empty list, null or check how to fill the list from somewhere else
}
try {
final response = await http.get(url);
final SharedPreferences preferences = await SharedPreferences.getInstance();
if (200 == response.statusCode) {
final String utfData = utf8.decode(response.bodyBytes); //just decode it yourself instead of using response.body which uses [latin1] by default
final List<Data> data = await compute(dataFromJson, utfData);
final file = await _createFile();
Map<String, dynamic> args = {
'file': file,
'data': utfData
//'data': response.body // pass the return body instead of the data
};
await compute(writeFile, args);
await preferences.setString('updateDate', dateApi.toString()); //Save the new date
return data;
} else {
return List<Data>();
}
} catch (e) {
return List<Data>();
}
}
File _createFile() async{
Directory tempDir = await getTemporaryDirectory(); // or check for a cache dir also
return File('${tempDir.path}/Data.json');
}
Future<bool> get compareLastUpdate async{
final dateCache = await _lastUpdateDB;
dateApi = await _lastUpdateApi;
if(dateCache == null) return false;
return dateApi?.isAtSameMomentAs(dateCache) ?? false; // or just isAfter()
// If dateApi is null (an error conection or some throw) just return false or throw an error and
// catch it somewhere else (and give info to the user why it couldn't update)
}
Future<DateTime> get _lastUpdateApi async{
try {
final response = await http.get(urlUpdate);
DateTime dateTime;
if (200 == response.statusCode) {
final data = jsonDecode(response.body));
dateTime = DateTime.tryParse(data['updated'] ?? '');
}
return dateTime;
} catch (e) {
return null;
}
}
Future<DateTime> get _lastUpdateDB async{
final SharedPreferences preferences = await SharedPreferences.getInstance();
return DateTime.tryParse(preferences.getString('updateDate') ?? ''); // Or if it's null use an old date
// The first time the app opens there is no updateDate value, so it returns null, if that
// happens replace it by an old date, one you know your api will be always newer,
// Ex: 1999-08-06 02:07:53.973 Your Api/App didn't even exist back then
// Or just use an empty String so the tryParser returns null
}
}
Then in the widget you just call it the same
class _LoadDataPageState extends State<LoadDataPage> {
final DataServices services = DataServices();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Load Data')),
body: FutureBuilder(
future: services.getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<Widget> children;
List<Data> _data = snapshot.data;
if (snapshot.hasData) {
return ListView.builder(
itemCount: _data.length,
itemBuilder: (context, index) {
return Column(
children: [Text(_data[index].data)],
);
},
);
} else {
children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
}
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
}));
}
}
Also yu could check Dio package which have some functions over http that let you add parameters to the url