0

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!

2 Answers 2

1

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,
            ),
          );
        }
      },
    );
Sign up to request clarification or add additional context in comments.

1 Comment

I had to add null checks to it and change the bottom to be like this: ${snapshot.data[index]['Duration'] + ' - ' + snapshot.data[index]['type']} but it worked. I also had to bear in mind about property names being case sensitive. Thanks!
0

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

6 Comments

itemCount: snapshot.data.length I have added this and I getting this error in the debug console: The property 'length' can't be unconditionally accessed because the receiver can be 'null'. Try making the access conditional (using '?.') or adding a null check to the target ('!').
Under the line snapshot.data[index].title, I am also getting the error The method '[]' can't be unconditionally invoked because the receiver can be 'null'. Try making the call conditional (using '?.') or adding a null check to the target ('!'). I assume this is a null safety thing
@danny471 it's bc of dart null safety feature, add ! to them eg. snapshot.data![index] snapshot.data!.length
I tried that but I get this error in the debug console now: ════════ Exception caught by widgets library ═══════════════════════════════════ Class '_InternalLinkedHashMap<String, dynamic>' has no instance getter 'title'. Receiver: _LinkedHashMap len:5 Tried calling: title
itemCount: snapshot.data.length does seem to be working though as it's getting the right number of items in the json. So it's not an issue with the JSON not coming back, it doesn't seem to be able to get the properties.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.