I'm new in flutter and I have issue with parsing JSON on HTTP response.
I'm using Airtable backend, to store information about posts. These always contain images, and sometimes attachments - PDFs.
I built PODO, like this:
class Post {
String recordid;
String timecreated;
String title;
String content;
String imgurl;
List<Pdf>? pdf;
Post({
required this.recordid,
required this.timecreated,
required this.title,
required this.content,
required this.imgurl,
required this.pdf
});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
// fields: Fields.fromJson(json['fields']),
recordid: json['id'] as String,
timecreated: json['createdTime'] as String,
title: json['fields']['field1'] as String,
content: json['fields']['field2'] as String,
imgurl: json['fields']['IMG'][0]['url'] as String,
pdf: json['fields']['PDF'] == null ? null : List<Map<String, dynamic>>.from(json['fields']['PDF']).map((dynamic value) => Pdf.fromJson(value)).toList()
);
}
Map<String, dynamic> toJson() => {
"recordid": recordid,
"timecreated": timecreated,
"title": title,
"content": content,
"imgurl": imgurl,
"pdf": pdf
// "fields": List<dynamic>.from(fields.map((x) => x.toJson())),
};
}
class Pdf {
Pdf({
required this.url,
required this.filename
});
Pdf.fromJson(Map<String, dynamic> json) :
url = json['url'],
filename = json['filename'];
final String? url;
final String? filename;
}
I'm not getting any errors, but when I'm trying to use PDF URL in UI, eg. in Text:
ListTile(title: Text(post.pdf.url)),
I'm getting error:
The property 'url' can't be unconditionally accessed because the
receiver can be 'null'.
I'm aiming to create a button on a page, that is clickable when PDF URL exists. When it exists, button navigates to PDF view that use PDF URL to get and display PDF.
Any ideas?
The pdf attribute is nullable, hence it cannot be accessed unconditionally. This is assuming you somehow have the pdf not as a list, otherwise you would need to index your list, your code is incomplete. You could try to do something like this:
if (post.pdf != null) {
//wrap it with a button or whatever
return ListTile(title: Text(post.pdf!.url));
}
else {
return Text('no pdf');
}
Well, sth seems to work, but still cannot parse pdf URL.
I'm getting Text "sth is there" when post.pdf != null and it works, but when I'm changing to get value from model using post.pdf!.url I'm getting same error:
Try correcting the name to the name of an existing getter, or defining
a getter or field named 'url'.
child:Text(post.pdf!.url));
Here's piece of code:
LayoutBuilder(builder: (context, constraints) {
if(post.pdf != null){
return ElevatedButton(onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => PDFview(pdf: [])
)),
child:Text(post.pdf!.url));
}else{
return ElevatedButton(onPressed: null,
child:Text("no PDF"));
}
}
)
A fix in PODO worked for me.
Here's solution for my problem :)
pdf: json['fields']['PDF'] == null ? null : json['fields']['PDF'][0]['url'] as String,
Related
I'm trying to obtain the city/town and country's name from the JSON response after calling Google maps Platform's Place Autocomplete API, the original code is supposed to get a list of 5 "places" and display them on the screen for the user to pick from, I'm not sure how to extract the metadata from the JSON response to get the city and country names for each result and I don't know if that's possible, to be more specific, here's the complete code:
the code used to get the results of the JSON response:
Future<String?> placeAutoComplete(String query) async {
Uri uri = Uri.https(
"maps.googleapis.com",
"maps/api/place/autocomplete/json",
{
"input": query,
"key": apiKey,
},
);
String? response = await NetworkUtility.fetchUrl(uri);
return response;
}
AutocompletePredection:
class AutocompletePrediction {
/// [description] contains the human-readable name for the returned result. For establishment results, this is usually
/// the business name.
final String? description;
/// [structuredFormatting] provides pre-formatted text that can be shown in your autocomplete results
final StructuredFormatting? structuredFormatting;
/// [placeId] is a textual identifier that uniquely identifies a place. To retrieve information about the place,
/// pass this identifier in the placeId field of a Places API request. For more information about place IDs.
final String? placeId;
/// [reference] contains reference.
final String? reference;
AutocompletePrediction({
this.description,
this.structuredFormatting,
this.placeId,
this.reference,
});
factory AutocompletePrediction.fromJson(Map<String, dynamic> json) {
return AutocompletePrediction(
description: json['description'] as String?,
placeId: json['place_id'] as String?,
reference: json['reference'] as String?,
structuredFormatting: json['structured_formatting'] != null
? StructuredFormatting.fromJson(json['structured_formatting'])
: null,
);
}
}
class StructuredFormatting {
/// [mainText] contains the main text of a prediction, usually the name of the place.
final String? mainText;
/// [secondaryText] contains the secondary text of a prediction, usually the location of the place.
final String? secondaryText;
StructuredFormatting({this.mainText, this.secondaryText});
factory StructuredFormatting.fromJson(Map<String, dynamic> json) {
return StructuredFormatting(
mainText: json['main_text'] as String?,
secondaryText: json['secondary_text'] as String?,
);
}
}
the code used to format the response:
class PlaceAutocompleteResponse {
final String? status;
final List<AutocompletePrediction>? predictions;
PlaceAutocompleteResponse({this.status, this.predictions});
factory PlaceAutocompleteResponse.fromJson(Map<String, dynamic> json) {
return PlaceAutocompleteResponse(
status: json['status'] as String?,
// ignore: prefer_null_aware_operators
predictions: json['predictions'] != null
? json['predictions']
.map<AutocompletePrediction>(
(json) => AutocompletePrediction.fromJson(json))
.toList()
: null,
);
}
static PlaceAutocompleteResponse parseAutocompleteResult(
String responseBody) {
final parsed = json.decode(responseBody).cast<String, dynamic>();
return PlaceAutocompleteResponse.fromJson(parsed);
}
}
and finally, the code used to combine both of the above methods:
List<AutocompletePrediction> placePredictions = [];
void placeAutoCompleteCreateTrip(String query) async {
final response = await placeAutoComplete(query);
if (response != null) {
PlaceAutocompleteResponse result =
PlaceAutocompleteResponse.parseAutocompleteResult(response);
if (result.predictions != null) {
setState(() {
placePredictions = result.predictions!;
});
}
}
}
So, how can I get the city and country's name from each result in the response?
AN IMPORTANT NOTE
there's this weird thing in the "place autocomplete" API where if the user types the location in a non-English language like Arabic or Urdu, the country in the results appears first in opposite to English where the country appears last, is this intentional and can you still extract the country and city name from the result even if the language is not English?
I've tried formatting the code and following the instructions in this link from GCP "https://developers.google.com/maps/documentation/places/web-service/autocomplete" but I was pretty confused and lost, I saw a similar answer for my question but it was in JS and I tried to replicate it but it didn't work.
Is there a way we can make the links in Flutter clickable if they are being fetched via JSON API? I mean, I can see that my links get a different color, but when I try to click on it, nothing happens. Trying it on an emulator, have not released the app yet.
JSON:
"content": {
"rendered": "<p>Absolutely great movie Test!</p>\n"},
I need to make sure that "Test" is clickable and sends me to the post in my app.
This is the content JSON file:
class Content {
String? raw;
String? rendered;
bool? protected;
int? blockVersion;
Content({this.rendered});
Content.fromJson(Map<String, dynamic> json) {
raw = json['raw'];
rendered = json['rendered'];
protected = json['protected'];
blockVersion = json['block_version'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['raw'] = this.raw;
data['rendered'] = this.rendered;
data['protected'] = this.protected;
data['block_version'] = this.blockVersion;
return data;
}
}
How can I make them clickable automatically?
#override
void initState() {
super.initState();
_content = widget.post.content?.rendered ?? "";
_content = _content.replaceAll('localhost', '192.168.6.165');
}
The text itself:
Html(
data: _content // this is where all texts are,
// blockSpacing: 0.0,
),
If I use RichText, it gives me the following error:
error: The argument type 'String' can't be assigned to the parameter
type 'InlineSpan'. (argument_type_not_assignable)
RichText(
text: TextSpan(
text: 'Hello ',
style: DefaultTextStyle.of(context).style,
children: <TextSpan>[
TextSpan(
text: 'world!',
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text: ' click here!',
recognizer: TapGestureRecognizer()
..onTap = () => print('click')),
],
),
);
Okay, I partially solved it.
Still, the problem I have with this is that it opens a browser - I need it to go to the posts itself on my app, but I can't get this to work. Just sharing anyway.
Html(
data: _content,
onLinkTap: (url, _, __, ___) async {
if (await launch(url!)) {
await launch(
url,
);
}
},
),
Trying to create an if statement that checks if an API returns 'items' containing data
Here is the API url, you can see that items is empty for that specific data query https://data.food.gov.uk/food-alerts/id?since=2021-01-04T00:00:00Z
My code is below, been trying to see if I can check 'items' to see if it is null, as well as checking for its length and see if it equals 0 but has not worked so far
class AllergyAlert {
final String title;
AllergyAlert({this.title});
factory AllergyAlert.fromJson(Map<String, dynamic> json) {
if (json['items'] == null) {
return AllergyAlert(
title: 'No new allergy alerts',
);
} else {
return AllergyAlert(
title: json['items'][0]['title'],
);
}
}
}
You can try this
return AllergyAlert(
title: json['items'].isEmpty ? 'No new allergy alerts' : json['items'][0]['title'],
);
First create a class to decode your json string with. You can paste your json here Quicktype
This can then be used with a FutureBuilder to load the data. Here is a full example
Solution
Then after that you will just use the defined names to check. Like:
class Allergen {
Allergen({
this.meta,
this.items,
});
final Meta meta;
final List<dynamic> items;
factory Allergen.fromJson(Map<String, dynamic> json) => Allergen(
meta: Meta.fromJson(json["meta"]),
items: List<dynamic>.from(json["items"].map((x) => x)),
);
Map<String, dynamic> toJson() => {
"meta": meta.toJson(),
"items": List<dynamic>.from(items.map((x) => x)),
};
}
class Meta {
Meta({...//more here
you can just check the length of the list
Allergen.items.length==0?//Do This://Else This
I'm trying to create Dart classes for FHIR resources defined in Json. The full Json schema for FHIR is here if anyone wants to look. My issue is with a oneOf declaration. Specifically, I have a class like the following (I'm not including the full class definition here, although I can if anyone thinks it would be helpful):
class Bundle_Entry {
Resource resource;
Bundle_Entry(this.resource});
factory Bundle_Entry.fromJson(Map<String, dynamic> json) => _$Bundle_EntryFromJson(json);
Map<String, dynamic> toJson() => _$Bundle_EntryToJson(this);
}
My problem is that ResourceList is defined as oneOf a number of other classes.
"ResourceList": {
"oneOf": [
{ "$ref": "#/definitions/Account" },
{ "$ref": "#/definitions/ActivityDefinition" },
...
]
}
I've tried declaring the 'resource' variable as types 'var', 'dynamic', and 'ResourceList' and created a class ResourceList that just contains a resource.
Each resource has a field titled 'resourceType', so I've also tried creating a ResourceList function that returns different types based on argument of 'resourceType', which also doesn't work.
If I do an http request, the actual response I'm trying to parse looks like this:
{
"resourceType": "Bundle",
"type": "searchset",
"entry": [
{
"resource": {
"name": "Jaba the Hutt"
"birthDate": "1980-07-27",
"id": "b26646dd-c549-4981-834e-bb4145d104b8",
"resourceType": "Patient"
}
}
]
}
Any suggestions would be appreciated.
Updating my question. It's interesting, that the first answer is similar to what I've come up with currently.
class Bundle_Entry {
dynamic resource;
Bundle_Entry({this.resource});
Bundle_Entry.fromJson(Map<String, dynamic> json) =>
Bundle_Entry(
resource: json['resource'] == null
? null
: ResourceTypes(
json['resource']['resourceType'], json['resource'] as Map<String, dynamic>)
);}
Map<String, dynamic> toJson() => _$Bundle_EntryToJson(this);
}
dynamic ResourceTypes(String type, Map<String, dynamic> json) {
if (type == 'Element') return (new Element.fromJson(json));
if (type == 'Extension') return (new Extension.fromJson(json));
if (type == 'Patient') return (new Narrative.fromJson(json));
My issue is that then I have to hard code each ResourceType, and it seemed like there should be an easier way.
I have been trying to build a similar thing. I approached it using inheritance and created a function to return the resource based on the ResourceType field.
Resource getResource(json) {
if(json == null) return null;
if(json["resourceType"] == "Patient") return Patient.fromJson(json);
if(json["resourceType"]=="Procedure") return Procedure.fromJson(json);
if(json["resourceType"]=="Encounter") return Encounter.fromJson(json);
if(json["resourceType"]=="Appointment") return Appointment.fromJson(json);
if(json["resourceType"]=="Slot") return Slot.fromJson(json);
if(json["resourceType"]=="Slot") return Slot.fromJson(json);
if(json["resourceType"]=="Observation") return Observation.fromJson(json);
if(json["resourceType"]=="Condition") return Condition.fromJson(json);
if(json["resourceType"]=="DiagnosticReport") return DiagnosticReport.fromJson(json);
if(json["resourceType"]=="MedicationRequest") return MedicationRequest.fromJson(json);
if(json["resourceType"]=="CarePlan") return CarePlan.fromJson(json);
if(json["resourceType"]=="Practitioner") return Practitioner.fromJson(json);
if(json["resourceType"]=="AllergyIntolerance") return AllergyIntolerance.fromJson(json);
return Resource.fromJson(json);
}
Where Patient, Procedure, etc. are subclasses of Resource:
class Entry {
String fullUrl;
Resource resource;
factory Entry.fromJson(Map<String, dynamic> json) => Entry(
fullUrl: json["fullUrl"] == null ? null : json["fullUrl"],
//This line is the magic
resource: getResource(json["resource"]),
search: json["search"] == null ? null : Search.fromJson(json["search"]),
response: json["response"] == null
? null
: Response.fromJson(json["response"]),
);
While not ideal, I did end up using the answer above, just modified slightly.
Resource _resourceFromJson(Map<String, dynamic> json) {
final dynamic resourceType = json['resourceType'];
switch (resourceType) {
case 'Account':
return Account.fromJson(json);
case 'ActivityDefinition':
return ActivityDefinition.fromJson(json);
case 'AdministrableProductDefinition':
return AdministrableProductDefinition.fromJson(json);
case 'AdverseEvent':
return AdverseEvent.fromJson(json);
case 'AllergyIntolerance':
return AllergyIntolerance.fromJson(json);
...
For anyone who runs into this. Full file is here
how to update JSON value. I am using flutter with a REST API to change the data but I'm struggling how to refresh my JSON data sorry friends I don't know much about the flutter so please help me out about it please find JSON'S Object below:
{
id: 1,
clef: "Y8F5eEo0",
IndiceSensibilite: "0.04",
Objectif: "1.00"
}
I want to update the value of IndiceSensibilite using a textField.
I m here for more clarification.
i will be very thankful if there's someone who gonna help me.
You can transform the JSON into an Object, make your modifications and back to JSON:
import 'dart:convert'; // make sure you imported this library
String jsonValue = '{"id": 1, "clef": "Y8F5eEo0", "indiceSensibilite": "0.04", "objectif": "1.00" }'; // original JSON
Use a Future to convert it into Object:
Future<ToObject> jsonToObject() async {
var parse = json.decode(jsonValue);
return ToObject.parseJson(parse);
}
and your Object (ToObject in my case) will have the following model and methods:
class ToObject {
int id;
String clef;
String indiceSensibilite;
String objectif;
ToObject({this.id, this.clef, this.indiceSensibilite, this.objectif});
factory ToObject.parseJson(Map<String, dynamic> json) => ToObject(
id: json['id'], clef: json['clef'], indiceSensibilite: json['indiceSensibilite'], objectif: json['objectif']);
Map<String, dynamic> toJson() =>
{'id': id, 'clef': clef, 'indiceSensibilite': indiceSensibilite, 'objectif': objectif};
}
I used a FutureBuilder to get the data, modify the value and back to JSON:
FutureBuilder<ToObject>(
future: jsonToObject(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var object = snapshot.data;
object.indiceSensibilite = "43.00";
return Text(json.encode(object.toJson()));
} else {
return Text("Loading...");
}
},
)
Your end result is a modified JSON to use as you wish: