0

I am very new to Flutter and have been trying for a few days now to get this right. Either I get stuck with the set state that does not reload(With a spinner) or the JSON cant is decoded as my example.

I want to get the JSON into a listview and then when the floatingActionButton button is pushed refresh the widget with the CircularProgressIndicator. This is what I have so far. All examples that I find seem not to be Null safe and again I am lost.

My example says "List' is not a subtype of type 'Map<String, dynamic>" and I can see this is because my JSON is a list List ??.

A few pointers would be greatly appreciated.

This is the JSON its pulling:

[
  {
    "userId": 1,
    "id": 1,
    "title": "How to make friends"
  },
  {
    "userId": 2,
    "id": 1,
    "title": "Gone with the wind"
  }
]
```
Future<Album> fetchAlbum() async {
  print("Fetching json....");
  final response = await http.get(
    Uri.parse(myLocksEp),
  );

  if (response.statusCode == 200) {
    print(response.body);
    return Album.fromJson(jsonDecode(response.body));
  } else {
    throw Exception('Failed to load album');
  }
}

class Album {
  final int id;
  final String title;

  Album({required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'],
      title: json['name'],
    );
  }
}

void main() {
  runApp(mylocks());
}

class mylocks extends StatefulWidget {
  @override
  _MyAppState createState() {
    return _MyAppState();
  }
}

class _MyAppState extends State<mylocks> {
  late Future<Album> _futureAlbum;

  @override
  void initState() {
    super.initState();
    _futureAlbum = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8.0),
          child: FutureBuilder<Album>(
            future: _futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                if (snapshot.hasData) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[],
                  );
                } else if (snapshot.hasError) {
                  return Text('Error: ${snapshot.error}');
                  //Error message goers here
                }
              }
              return const CircularProgressIndicator();
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.refresh_outlined, color: Colors.black),
          backgroundColor: Colors.grey[200],
          onPressed: () {
            setState(() {
              _futureAlbum = fetchAlbum();
            });
          },
        ),
      ),
    );
  }
}

Sorry for the long post, I hope I make sense

Thank you in advance Robby

Milan Pansuriya
  • 2,521
  • 1
  • 19
  • 33
Robbal
  • 101
  • 1
  • 2
  • 8
  • If you get data from API refer my answer [here](https://stackoverflow.com/a/68709502/13997210) or [here](https://stackoverflow.com/a/68533647/13997210) or [here](https://stackoverflow.com/a/68594656/13997210) hope it's helpful to you – Ravindra S. Patil Sep 22 '21 at 11:52

2 Answers2

1

If I'm not wrong, here you are providing a List<Map<String, dynamic>>

return Album.fromJson(jsonDecode(response.body));

while the Album.fromJson is expecting to receive a Map<String, dynamic> So, thats why you are getting the error "List' is not a subtype of type 'Map<String, dynamic>"

Apart from that, take into account that every time you build the widget, FutureBuilder will always make a call to the future that you pass to it, so in this case, when you press the FloatingActionButton, you will make a call, and then, when the widget start to rebuild, you will make another one.

You should make some adjustments to avoid this. You could change the FloatingActionButton onPressed callback to be empty:

setState(() {});

Again, if I'm not wrong this will make the widget rebuild itself since you are saying that the state has changed, and when rebuilding, the FutureBuilder will make the request, and therefore, update the list.

EDIT BASED ON THE COMMENTS

class _MyAppState extends State<mylocks> {
  late Future<Album> _futureAlbum;
  //ADD ALBUMS VAR
  List<Album> _albums = [];

  @override
  void initState() {
    super.initState();
    //INVOKE METHOD AND SET THE STATE OF _albums WHEN IT FINISHES
    fetchAlbum().then((List<Album> albums){
      setState((){_albums = albums);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8.0),
          child: Column(
          //REPLACE FUTURE BUILDER WITH DESIRED WIDGET, WHILE _albums IS EMPTY IT WILL RENDER A CircularProgressIndicator
            children: [
                _albums.isEmpty ? Center(
                   child: CircularProgressIndicator(),
                 ) : ..._albums,
             ]
          ),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.refresh_outlined, color: Colors.black),
          backgroundColor: Colors.grey[200],
          onPressed: () {
            //AGAIN, WHEN FINISHING IT SHOULD REBUILD THE WIDGET AND THE DATA
            fetchAlbum().then((List<Album> albums){
                setState((){_albums = albums);
             });
          },
        ),
      ),
    );
  }
}
  • Thanks but if I use empty setState it reloads the data but it does not reload the widget with the Progress spinner. I have been strugging with this .. hence why i did it like this – Robbal Sep 22 '21 at 13:10
  • Mmm I see, try doing this: Remove the FutureBuilder, create a List in _MyAppState and in its initState call the fetchAlbum() method, then, call setState and assign its return to the newly created List variable. In the UI you can ask if the List is empty and return a loading indicator, and render the albums when the fetch is done. And for the onPressed callback, do the same, call the function and assign its return to the List with setState. I believe this approach will rebuild the widget when loading it for first time or when pressing the FloatingActionButton. – Fabián Bardecio Sep 22 '21 at 13:25
  • ok. Need to read this a few times – Robbal Sep 22 '21 at 13:28
  • Yeah sorry about that, I have edited my answer wit the comment idea – Fabián Bardecio Sep 22 '21 at 13:44
  • Fantasic .. let me check that out – Robbal Sep 22 '21 at 13:51
  • Thank you so much I am implemnting it now ...Your a star – Robbal Sep 22 '21 at 14:49
0

Don't worry about creating models for your Json just use this link to autogenerate a model.

Wali Khan
  • 586
  • 1
  • 5
  • 13