I am having trouble trying to iterate over a JSON array of objects from a remote URL using Flutter's FutureBuilder.
My goal is to:
Fetch JSON data from an API
Output the data into a 2 column gridview layout
The JSON data is an array of objects(or a List of Maps in dart), the objects have simple string data.
I know that I need to build a future to fetch the data from the API and decode the JSON, then I need to create a FutureBuilder to output the List data into my Gridview Builder. That is what I have tried to do in my code below.
import 'dart:convert';
import 'dart:async';
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class HomeSection extends StatefulWidget {
#override
_HomeSectionState createState() => _HomeSectionState();
}
class _HomeSectionState extends State<HomeSection> {
#override
void initState() {
super.initState();
}
Future<List<dynamic>> fetchSectionData() async {
String dataUrl =
'https://www.thisisthejsonapilink.co.uk/fetch-data';
var response = await http.get(Uri.parse(dataUrl));
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception('Failed to get the data');
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: fetchSectionData,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return GridView.builder(
itemCount: 12,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300,
crossAxisSpacing: 16.0,
mainAxisSpacing: 18.0,
childAspectRatio: MediaQuery.of(context).size.height / 930,
),
itemBuilder: (BuildContext context, int index) => GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => OtherScreen()),
);
},
child: Column(
children: [
Container(
margin: const EdgeInsets.only(bottom: 12.0),
height: 144,
),
Text(
snapshot.data[index].title,
),
Text(
snapshot.data[index].duration + ' - ' + snapshot.data[index].type,
),
],
),
),
);
} else {
return Center(
child: CircularProgressIndicator(
color: Colors.black,
),
);
}
},
);
}
}
My JSON that returns from the API link is structured like this:
[
{
"Title": "Audio 1",
"Duration": "5 min",
"type": "audio",
"bodyText": "lorem ipsum blah blah audio",
"url": "https://www.silvermansound.com/music/dirty-gertie.mp3"
},
{
"Title": "Video 1",
"Duration": "5 min",
"type": "video",
"bodyText": "lorem ipsum blah blah video",
"url": "https://assets.mixkit.co/videos/preview/mixkit-woman-wearing-a-mask-while-running-40158-large.mp4"
},
{
"Title": "Audio 2",
"Duration": "5 min",
"type": "audio",
"bodyText": "lorem ipsum blah blah audio",
"url": "https://www.silvermansound.com/music/dirty-gertie.mp3"
},
{
"Title": "Video 2",
"Duration": "5 min",
"type": "video",
"bodyText": "lorem ipsum blah blah video",
"url": "https://assets.mixkit.co/videos/preview/mixkit-woman-wearing-a-mask-while-running-40158-large.mp4"
},
{
"Title": "Audio 3",
"Duration": "5 min",
"type": "audio",
"bodyText": "lorem ipsum blah blah audio",
"url": "https://www.silvermansound.com/music/dirty-gertie.mp3"
},
{
"Title": "Video 3",
"Duration": "5 min",
"type": "video",
"bodyText": "lorem ipsum blah blah video",
"url": "https://assets.mixkit.co/videos/preview/mixkit-woman-wearing-a-mask-while-running-40158-large.mp4"
},
]
This is the error I am getting in my VS code debug console:
The argument type 'Future<List<dynamic>> Function()' can't be assigned to the parameter type 'Future<Object?>?'.
The red line appears right where I try to define my future in the FutureBuilder here under fetchSectionData:
return FutureBuilder(
future: fetchSectionData,
builder: (context, snapshot) {
I am not sure what this error means. Could somebody please explain it? The Future is definitely returning a <List>, but how do I get this list into the futurebuilder so that I can iterate over it and output the data into the gridview?
I am fairly new to flutter and come from a javascript web background, usually in javascript you can just loop over an array and output it that way. I'm tempted to do it that way here but I know that wouldn't be right.
When I looked up the Flutter documentation on how to fetch data from the internet it mentioned that I have to convert the response into a custom dart object, but nothing I try seems to work.
Appreciate your help!
Try this !
return FutureBuilder<List<dynamic>>(
future: fetchSectionData,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return GridView.builder(
itemCount: 12,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300,
crossAxisSpacing: 16.0,
mainAxisSpacing: 18.0,
childAspectRatio: MediaQuery.of(context).size.height / 930,
),
itemBuilder: (BuildContext context, int index) => GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => OtherScreen()),
);
},
child: Column(
children: [
Container(
margin: const EdgeInsets.only(bottom: 12.0),
height: 144,
),
Text(
'${snapshot.data[index]['Title']}',
),
Text(
'${snapshot.data[index]['Duration'] + ' - ' + snapshot.data[index]['type']}',
),
],
),
),
);
} else {
return Center(
child: CircularProgressIndicator(
color: Colors.black,
),
);
}
},
);
You have to pass the function like this
future: fetchSectionData(),
in your GridView it's better to use
...
itemCount: snapshot.data.length
...
List<dynamic> equals to List it self
Future<List> fetchSectionData() async{
...
it's a good practice to determine type of FutureBuilder
FutureBuilder<List>(
...
Related
What I'm trying to do is that if someone opens part 1 (JSON), then their respective article should be visible on the next screen. I was able to list out all the parts but how to show their respective articles on the next screen. For example, the first part contains 3 articles and the second part would contain 3
basically, every part would contain a different number of articles.
JSON
[
[ // Article Names
{
"ArtNo": "0",
"Name": "PREAMBLE",
},
{
"ArtNo": "1",
"Name": "Name and territory of the Union.",
},
{
"ArtNo": "2",
"Name": "Admission or establishment of new States.",
},
{
"ArtNo": "3",
"Name": "Formation of new States and alteration of areas, boundaries or names of existing States.",
},
{
"ArtNo": "4",
"Name": "Laws made under articles 2 and 3 to provide for the amendment of the First and the Fourth Schedules and supplemental, incidental and consequential matters.",
},
{
"ArtNo": "5",
"Name": "Citizenship at the commencement of the Constitution."
}
],
[ // Article Parts
{
"PartNo": "I",
"Name": "THE UNION AND ITS TERRITORY",
"Articles": ["1", "2", "3"]
},
{
"PartNo": "II",
"Name": "CITIZENSHIP",
"Articles": ["4", "5"]
}
]
]
Flutter (list out all parts)
class _ConstitutionPartsState extends State<ConstitutionParts> {
Future parts() async {
final data = await rootBundle.loadString('assets/json/file.json');
final jsonResult = jsonDecode(data);
final parts = jsonResult[1];
return parts;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: parts(), builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return const CircularProgressIndicator(color: Colors.deepOrangeAccent);
}
return Container(
margin: const EdgeInsets.all(25.0),
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: const EdgeInsets.only(bottom: 20.0),
child: ListTile(
title: Text(snapshot.data[index]['Name']),
),
);
},
),
);
},
),
);
}
}
any help would be appreciated! thank you
You can use where condition for filteration,
more info
Here's the Full Code
class _ConstitutionPartsState extends State<ConstitutionParts> {
Future parts() async {
final data = await rootBundle.loadString('assets/json/file.json');
final jsonResult = jsonDecode(data);
return jsonResult; // return whole json
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: parts(), builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return const CircularProgressIndicator(color: Colors.deepOrangeAccent);
}
return Container(
margin: const EdgeInsets.all(25.0),
child: ListView.builder(
itemCount: snapshot.data[1].length,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: const EdgeInsets.only(bottom: 20.0),
child: ListTile(
title: Text(snapshot.data[index]['Name']),
onTap():{
//navigate to name page with
NewPage(articePartIndex : index, json: snapshot.data)
}
),
);
},
),
);
},
),
);
}
}
NewPage {
final List<List<dynamic>> json;
final int index;
#override
Widget build(BuildContext context) {
var articles = json[1][index]['Articles'];
var filteredArticleList = json[0].where((a) => articles.contains(a['ArtNo'])).toList();
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(filteredArticleList[index]['Name']),
),
});
}
}
I am trying to create a quizapp in which i create cards, like when user click on specific card then it will be redirect to that quizpage.
here is the where i am trying to get json file data which are quiz questions
class getjson extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future:DefaultAssetBundle.of(context).loadString("assets/questions/generalQ.json"),
builder: (context, snapshot) {
List mydata = json.decode(snapshot.data.toString());
print(mydata);
if (mydata == null) {
return Scaffold(
body: Center(
child: Text(
"Loading",
),
),
);
} else {
return quizpage();
}
},
);
}
}
class quizpage extends StatefulWidget {
#override
_quizpageState createState() => _quizpageState();
}
class _quizpageState extends State<quizpage> {
#override
Widget build(BuildContext context) {
return Container(
);
}
}
and here i created cards widgets
Widget customcard(String langname, String image, String des){
return Padding(
padding: EdgeInsets.symmetric(
vertical: 20.0,
horizontal: 30.0,
),
child: InkWell(
onTap: (){
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context)=>getjson(),));
},
i am calling getjson class on the onTap.
when i run the app click on card it shows me "loading", it is returning null on "mydata" of getjson class.
how to fix it? why it's returning null on mydata? Please help!
JSON FILE:
[
{
"1": "What is the world's most populated country?",
"2": "Each year World Red Cross and Red Crescent Day is celebrated on",
"3": "The ozone layer restricts"
},
{
"1": {
"a": "Russia",
"b": "China",
"c": "USA"
},
"2": {
"a": "May 8",
"b": "May 18",
"c": "April 28"
},
"3": {
"a": "Visible light",
"b": "Ultraviolet radiation",
"c": "Infrared radiation"
}
}
]
Actually it's pretty simple, you say that your list = a Future . When you do json.decode(snapshot.data) the Future of your futureBuilder is not yet finished, so mydata at that time is snapshot.data which is null. You should write:
if(snapshot.ConnectionState == ConnectionState.done){
List mydata = json.decode(snapshot.data.toString());
return quizpage();
}else{
return Scaffold(
body: Center(
child: Text(
"Loading",
),
),
);
}
I have a JSON list of products of two companies, Apple and Windows. The below is my JSON data structure:
[
{
"category": "Apple",
"name": "Macbook Air"
},
{
"category": "Apple",
"name": "Macbook Pro"
},
{
"category": "Microsoft",
"name": "Surface"
},
{
"category": "Apple",
"name": "iPad"
},
{
"category": "Microsoft",
"name": "Windows"
},
{
"category": "Apple",
"name": "Siri"
},
{
"category": "Microsoft",
"name": "Office"
}
]
I am trying to create a listview of either Apple or Microsoft category. But in my current listview, it shows all the products from the JSON file:
List data;
Future<String> loadJsonData() async{
var jsonText = await rootBundle.loadString('assets/json/products.json');
setState(() => data = json.decode(jsonText));
}
#override
void initState() {
this.loadJsonData();
super.initState();
}
The above code loads all the products of both companies. But how can I load only the products of Apple or Microsoft?
This is my ListView
Container(
child: data == null ? Container() : ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: ListTile(
title: Text(
data[index]['name'],
),
),
);
}
),
),
You can do something like this:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfff5f5f5),
appBar: AppBar(
title: Text('Demo'),
),
body: Container(
child: data == null
? Container()
: ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: ((data[index]['category'] == "Apple")
? ListTile(title: Text(data[index]['name']))
: Container()));
}),
));
}
}
You can remove items except for Apple or Microsoft:
data.removeWhere((item) {
item['category'] != 'Apple' || item['category'] != 'Microsoft'
});
See also List Dartdoc
How to parse the JSON to list in flutter. I have used the online tool to parse the Json, but the tool is converting it to the map. I need to get the parsed json in the list and return the list of contents in listview builder of the page. I have my json file and my workaround below.
Json
{
"batchcomplete": "",
"continue": {
"gcmcontinue": "page|41434143494120464552525547494e4541202d204152494d45444148|3704",
"continue": "gcmcontinue||"
},
"query": {
"pages": {
"225": {
"pageid": 225,
"ns": 0,
"title": "Abrus precatorius - Gunja",
"thumbnail": {
"source": "https://example.org/images/thumb/c/cb/Abrus_precatorius_%281463017430%29.jpg/600px-Abrus_precatorius_%281463017430%29.jpg",
"width": 600,
"height": 450
},
"pageimage": "Abrus_precatorius_(1463017430).jpg"
},
"625": {
"pageid": 625,
"ns": 0,
"title": "Abies webbiana - Talispatra",
"thumbnail": {
"source": "https://example.org/images/thumb/b/b1/Red_fir.jpg/397px-Red_fir.jpg",
"width": 397,
"height": 600
},
"pageimage": "Red_fir.jpg"
},
"15995": {
"pageid": 15995,
"ns": 0,
"title": "Abelmoschus esculentus - Bhenda",
"thumbnail": {
"source": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/India_-_Koyambedu_Market_-_Ladies_Finger_03_%283986242135%29.jpg/600px-India_-_Koyambedu_Market_-_Ladies_Finger_03_%283986242135%29.jpg",
"width": 600,
"height": 450
},
"pageimage": "India_-_Koyambedu_Market_-_Ladies_Finger_03_(3986242135).jpg"
}
}
},
"limits": {
"pageimages": 500
}
}
Herbslist.dart
class Herbs extends StatefulWidget {
final String title;
Herbs(this.title);
#override
_HerbsState createState() => new _HerbsState();
}
class _HerbsState extends State<Herbs> {
var cname;
Future<Herbslist> fetchPost() async {
final response = await http.get(
'https://example.org/api.php?action=query&gcmtitle=Category:$cname&pilimit=max&prop=pageimages&pithumbsize=600&generator=categorymembers&format=json&gcmcontinue='
);
if (response.statusCode == 200) {
if(this.mounted){
return Herbslist.fromJson(json.decode(response.body));
}
} else {
print(Exception);
throw (e) {
print("Exception thrown: $e");
Exception(e);
};
}
}
#override
Widget build(BuildContext context) {
cname = widget.title;
return new Scaffold(
appBar: AppBar(
title: Align(
alignment: Alignment(-0.2, 0.3),
child: Text(
cname,
),
),
),
body: Center(
child: FutureBuilder<Herbslist>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.query.pages.length,
itemBuilder: (BuildContext context, int index) {
// var gcm = snapshot.data.herbslistContinue.gcmcontinue;
var img = snapshot.data.query.pages.values
.toList()[index]
.thumbnail
.source;
return Container(
child: Card(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Detailpage(
snapshot.data.query.pages.values
.toList()[index]
.title,
),
));
},
child: ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 8.0, vertical: 8.0),
leading: Container(
padding: EdgeInsets.only(right: 10.0),
decoration: new BoxDecoration(
border: new Border(
right: new BorderSide(
width: 1.5, color: Colors.grey)),
),
// ignore: unrelated_type_equality_checks
child: img == img.isEmpty
? SizedBox(
height: 50.0,
width: 50.0,
child: Image.asset('image.png'),
)
: SizedBox(
height: 50.0,
width: 50.0,
child: FadeInImage.assetNetwork(
placeholder: 'image.png',
image: img,
fit: BoxFit.fill,
),
)),
title: Text(snapshot.data.query.pages.values
.toList()[index]
.title),
),
)));
},
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
));
}
}
[1]: https://app.quicktype.io/
You can do something as simple as this, assuming the variable jsonMap is the json you showed above:
jsonMap['query']['pages'].values.toList()
Edit: Edited to change to better solution suggested in the comments.
Here is an example of how you could apply it on your code:
FutureBuilder<Herbslist>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List pages = snapshot.data['query']['pages'].values.toList();
return ListView.builder(
shrinkWrap: true,
itemCount: pages.length,
Make sure to change the rest of your code to use the new pages variable.
this json has two data first only name and second is inside there also name which is service name. ex 'Travel and Stay' and 'Religious' is main name which has to be displayed in expansion tile name and The 'Church/ Baptism' and 'Hindu Temples' is a subitem which is displayed inside checkbox list tile. Hope you understand :slightly_smiling_face: Please Help me
class _MyHomePageState extends State<MyHomePage> {
var lovCountryServices = [
{
"services": [
{
"service_group_id": 22,
"service_category": "B",
"name": "Air Tickets",
"id": 228
},
{
"service_group_id": 22,
"service_category": "W",
"name": "Boys Hostel",
"id": 229
},
],
"name": "Travel and Stay",
"is_active": true,
"id": 22
},
{
"services": [
{
"service_group_id": 19,
"service_category": "B",
"name": "Church/ Baptism",
"id": 193
},
{
"service_group_id": 19,
"service_category": "B",
"name": "Hindu Temples",
"id": 194
}
],
"name": "Religious",
"is_active": true,
"id": 19
}
];
List<_Result> _results = [];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: lovCountryServices.length,
itemBuilder: (BuildContext context, int index) {
var item = lovCountryServices[index];
var items= lovCountryServices[index]['services'];
return ExpansionTile(
title: Text(item['name']),
children: <Widget>[
CheckboxListTile(
title: Text("temp"),
value: item['is_active'],
onChanged: (val) {
setState(() {
item['is_active'] = val;
});
},
),
],
);
},
),
RaisedButton(
onPressed: () => print("sending to backend"),
child: Text("SEND"),
)
],
)),
);
}
}
I want thw data in checkbox list tile right there is dummy data called TEMP and i want the data from json right now in json there is 'Boys Hostel' this data needs to comes inside the checkbox listtile. Hope you undestand please help me
Working Code: You can use a Map variable to store boolean value.
Map<String, bool> _selection = {};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dummy'),
),
body: Center(
child: Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: lovCountryServices.length,
itemBuilder: (BuildContext context, int index) {
var item =
lovCountryServices[index]; // should be outside build function
List items = item['services']; // should be outside build function
return ExpansionTile(
title: Text(item['name']),
children: List.generate(items.length, (i) {
_selection[items[i]['name']] =
_selection[items[i]['name']] ?? item['is_active'];
return CheckboxListTile(
title: Text(items[i]['name']),
value: _selection[items[i]['name']],
onChanged: (val) {
setState(() {
_selection[items[i]['name']] = val;
});
},
);
}),
);
},
),
RaisedButton(
onPressed: () => print("sending to backend"),
child: Text("SEND"),
)
],
)),
);
}