How to parse a list inside JSON in flutter

Viewed 4282

I am trying to make API calls in a flutter. While parsing a simple JSON it works fine but while I try to parse a List it shows error how can I do that?

My JSON

  {
  "posts": [
    {
      "id": 1,
      "title": "Post 1"
    },
    {
      "id": 2,
      "title": "Post 2"
    },
    {
      "id": 3,
      "title": "Post 3"
    }
  ],
  "comments": [
    {
      "id": 1,
      "body": "some comment",
      "postId": 1
    },
    {
      "id": 2,
      "body": "some comment",
      "postId": 1
    }
  ],
  "profile": {
    "name": "typicode"
  }
}

My PODO Class

    class Welcome {
    List<Post> posts;
    List<Comment> comments;
    Profile profile;

    Welcome({
        this.posts,
        this.comments,
        this.profile,
    });

    factory Welcome.fromJson(Map<String, dynamic> json) => Welcome(
        posts: List<Post>.from(json["posts"].map((x) => Post.fromJson(x))),
        comments: List<Comment>.from(json["comments"].map((x) => Comment.fromJson(x))),
        profile: Profile.fromJson(json["profile"]),
    );

    Map<String, dynamic> toJson() => {
        "posts": List<dynamic>.from(posts.map((x) => x.toJson())),
        "comments": List<dynamic>.from(comments.map((x) => x.toJson())),
        "profile": profile.toJson(),
    };
}

class Comment {
    int id;
    String body;
    int postId;

    Comment({
        this.id,
        this.body,
        this.postId,
    });

    factory Comment.fromJson(Map<String, dynamic> json) => Comment(
        id: json["id"],
        body: json["body"],
        postId: json["postId"],
    );

    Map<String, dynamic> toJson() => {
        "id": id,
        "body": body,
        "postId": postId,
    };
}

class Post {
    int id;
    String title;

    Post({
        this.id,
        this.title,
    });

    factory Post.fromJson(Map<String, dynamic> json) => Post(
        id: json["id"],
        title: json["title"],
    );

    Map<String, dynamic> toJson() => {
        "id": id,
        "title": title,
    };
}

class Profile {
    String name;

    Profile({
        this.name,
    });

    factory Profile.fromJson(Map<String, dynamic> json) => Profile(
        name: json["name"],
    );

    Map<String, dynamic> toJson() => {
        "name": name,
    };
}

My api_call.dart file

import 'dart:convert';

import 'package:api/api/modal.dart';
import 'package:http/http.dart';

class HttpService{

  final String url = "https://my-json-server.typicode.com/typicode/demo/db";

  Future<List<Welcome>> getPost() async {

    Response response = await get(url);

    if(response.statusCode == 200) {
     List<dynamic> body = jsonDecode(response.body);
     List<Welcome> wel = body.map((e) => Welcome.fromJson(e)).toList();
     return wel;
    }

  }
}

My widget.dart

import 'package:api/api/api_fetcher.dart';
import 'package:api/api/modal.dart';
import 'package:flutter/material.dart';

class News extends StatefulWidget {

  @override
  _NewsState createState() => _NewsState();
}

class _NewsState extends State<News> {

  final HttpService http = HttpService();

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: DefaultTabController(
        length: 4,
        child: Scaffold(
          backgroundColor: Colors.white,
          appBar: AppBar(
            backgroundColor: Colors.white,
            title: Text(
              'Trending Topics',
              style: TextStyle(color: Colors.black, fontSize: 25),
            ),
            automaticallyImplyLeading: false,
            elevation: 0,
            actions: <Widget>[
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 10),
                child: Center(
                  child: Text(
                    'see all',
                    style: TextStyle(fontSize: 20, color: Colors.grey[600]),
                  ),
                ),
              ),
            ],
            bottom: TabBar(
              labelColor: Colors.black,
              unselectedLabelColor: Colors.black,
              tabs: <Widget>[
                Tab(
                  text: 'Tech',
                ),
                Tab(
                  text: 'Art',
                ),
                Tab(
                  text: 'Sports',
                ),
                Tab(
                  text: 'Nation',
                ),
              ],
            ),
          ),
          body: TabBarView(
            children: <Widget>[
              Container(
                child: FutureBuilder(
                  future: http.getPost(),
                  builder: (context, snapshot) {
                    if(snapshot.hasData){
                      List<Welcome> welc = snapshot.data;
                      return ListView(
                        children: welc.map((Welcome welcome) => ListTile(
                          title: Text(welcome.posts.length.toString()),
                        )),
                      );
                      }
                  },
                ),
              ),
              Container(),
              Container(),
              Container(),
            ],
          ),
        ),
      ),
    );
  }
}

It shows me an error while I try to access the posts through the ListTile.

Give me a solution, please.

Thanks in advance.

2 Answers

This json have one Welcome class, so your function that getPost() should be return just Welcome, consequence you should change your widget that show just welcome with list of post and/or list of comment. Try like this:

class Welcome {
  List<Post> posts;
  List<Comment> comments;
  Profile profile;

  Welcome({
    this.posts,
    this.comments,
    this.profile,
  });

  factory Welcome.fromJson(Map<String, dynamic> json) {
    return Welcome(
      posts: json['posts'] != null
          ? json['posts'].map<Post>((json) => Post.fromJson(json)).toList()
          : null,
      comments: json['comments'] != null
          ? json['comments']
              .map<Comment>((json) => Comment.fromJson(json))
              .toList()
          : null,
      profile:
          json['profile'] != null ? Profile.fromJson(json['profile']) : null,
    );
  }
}

class HttpService {
  static String url = "https://my-json-server.typicode.com/typicode/demo/db";

  static Future<Welcome> getPost() async {
    Response response = await get(url);

    if (response.statusCode == 200) {
      final parsed = json.decode(response.body).cast<String, dynamic>();
      return Welcome.fromJson(parsed);
    }
  }
}

In your code, you used map to map some objects and then assigned them to children of a widget, also use .toList() after the map.

Something that I couldn't get from you code is that you have a Wellcome model that contains a list of posts and comments and a profile, your sample JSON is equal to one Welcome but in your code when you fetched the data you tried to map it as a list of Wellcome also in your widget.

The other thing is in the future builder, it's better to define what type it is going to get:

...
FutureBuilder<Welcome>(
    future: http.getPost(),
    builder: (context, AsyncSnapshot<Welcome> snapshot) {

...

Also future takes some times to complete so in cases it doesn't have data(not completed yet) you should return another widget, here I simply used:

return Center(child: CircularProgressIndicator());

I changed your code a little bit, here is the result:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MaterialApp(
    home: News(),
  ));
}

class Welcome {
  List<Post> posts;
  List<Comment> comments;
  Profile profile;

  Welcome({
    this.posts,
    this.comments,
    this.profile,
  });

  factory Welcome.fromJson(Map<String, dynamic> json) => Welcome(
        posts: List<Post>.from(json["posts"].map((x) => Post.fromJson(x))),
        comments: List<Comment>.from(
            json["comments"].map((x) => Comment.fromJson(x))),
        profile: Profile.fromJson(json["profile"]),
      );

  Map<String, dynamic> toJson() => {
        "posts": List<dynamic>.from(posts.map((x) => x.toJson())),
        "comments": List<dynamic>.from(comments.map((x) => x.toJson())),
        "profile": profile.toJson(),
      };
}

class Comment {
  int id;
  String body;
  int postId;

  Comment({
    this.id,
    this.body,
    this.postId,
  });

  factory Comment.fromJson(Map<String, dynamic> json) => Comment(
        id: json["id"],
        body: json["body"],
        postId: json["postId"],
      );

  Map<String, dynamic> toJson() => {
        "id": id,
        "body": body,
        "postId": postId,
      };
}

class Post {
  int id;
  String title;

  Post({
    this.id,
    this.title,
  });

  factory Post.fromJson(Map<String, dynamic> json) => Post(
        id: json["id"],
        title: json["title"],
      );

  Map<String, dynamic> toJson() => {
        "id": id,
        "title": title,
      };
}

class Profile {
  String name;

  Profile({
    this.name,
  });

  factory Profile.fromJson(Map<String, dynamic> json) => Profile(
        name: json["name"],
      );

  Map<String, dynamic> toJson() => {
        "name": name,
      };
}

class HttpService {
  final String url = "https://my-json-server.typicode.com/typicode/demo/db";

  Future<Welcome> getPost() async {
    var response = await http.get(url);

    if (response.statusCode == 200) {
      var body = json.decode(response.body);
      return Welcome.fromJson(body);
    }
  }
}

class News extends StatefulWidget {
  @override
  _NewsState createState() => _NewsState();
}

class _NewsState extends State<News> {
  final HttpService http = HttpService();

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: DefaultTabController(
        length: 4,
        child: Scaffold(
          backgroundColor: Colors.white,
          appBar: AppBar(
            backgroundColor: Colors.white,
            title: Text(
              'Trending Topics',
              style: TextStyle(color: Colors.black, fontSize: 25),
            ),
            automaticallyImplyLeading: false,
            elevation: 0,
            actions: <Widget>[
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 10),
                child: Center(
                  child: Text(
                    'see all',
                    style: TextStyle(fontSize: 20, color: Colors.grey[600]),
                  ),
                ),
              ),
            ],
            bottom: TabBar(
              labelColor: Colors.black,
              unselectedLabelColor: Colors.black,
              tabs: <Widget>[
                Tab(
                  text: 'Tech',
                ),
                Tab(
                  text: 'Art',
                ),
                Tab(
                  text: 'Sports',
                ),
                Tab(
                  text: 'Nation',
                ),
              ],
            ),
          ),
          body: TabBarView(
            children: <Widget>[
              Container(
                child: FutureBuilder<Welcome>(
                  future: http.getPost(),
                  builder: (context, AsyncSnapshot<Welcome> snapshot) {
                    if (snapshot.hasData) {
                      Welcome welc = snapshot.data;
                      return ListView(
                          children: welc.posts
                              .map((post) => ListTile(
                                    title: Text(post.title),
                                  ))
                              .toList());
                    }
                    return Center(child: CircularProgressIndicator());
                  },
                ),
              ),
              Container(),
              Container(),
              Container(),
            ],
          ),
        ),
      ),
    );
  }
}

Related