Flutter HTTP Client example with ListView – Fetch data and parse JSON in background

In this tutorial, we’re gonna build a Flutter App that use http package to fetch data from the internet, then parse JSON to a Dart List of Objects and display that List in ListView widget.

Related Post: Flutter ListView example with ListView.builder

Flutter App Overview

We will build a Flutter App that can display a List of Post objects gotten from JSONPlaceholder REST API. We are parsing JSON document that performs an expensive computation in the background.

flutter-http-example-listview-fetch-data-json-background

HTTP Fetch Data in background and Display in ListView

Add http package

Add this package to the dependencies section of our pubspec.yaml:

dependencies:
  http: 

Create DataModel class

class Post {
  final int userId;
  final int id;
  final String title;
  final String body;

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

  factory Post.fromJson(Map json) {
    return Post(
      userId: json['userId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
    );
  }
}

Parse JSON into a List of Objects

We will convert our HTTP Response into a list of Dart objects.

List<Post> parsePosts(String responseBody) {
  final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Post>((json) => Post.fromJson(json)).toList();
}

Fetch Data in a separate isolate

This is how we fetch data using the get method:

Future<List<Post>> fetchPosts(http.Client client) async {
  final response = await client.get('https://jsonplaceholder.typicode.com/posts');

  // compute function to run parsePosts in a separate isolate
  return parsePosts(response.body);
}

If we run the fetchPosts() function on a slower phone, the App may freeze for a moment when it parses and converts the JSON.

>> So we are moving the parsing and conversion to a background isolate using Flutter compute() function. This function runs parsePosts() in a background isolate and return the result.

Future<List<Post>> fetchPosts(http.Client client) async {
  final response = await client.get('https://jsonplaceholder.typicode.com/posts');

  // compute function to run parsePosts in a separate isolate
  return compute(parsePosts, response.body);
}

Display the data

In order to display fetched data on screen, we can use the FutureBuilder widget with two parameters:
– The Future to work with. In our case, the return of fetchPosts() function.
– A builder function that tells Flutter what to render, depending on the state of the Future: loading, success, or error.

FutureBuilder<List<Post>>(
  future: fetchPosts(http.Client()),
  builder: (context, snapshot) {
    if (snapshot.hasError) print(snapshot.error);

    return snapshot.hasData
        ? ListViewPosts(posts: snapshot.data) // return the ListView widget
        : Center(child: CircularProgressIndicator());
  },
),

ListView widget uses ListView.builder:

class ListViewPosts extends StatelessWidget {
  final List<Post> posts;

  ListViewPosts({Key key, this.posts}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView.builder(
          itemCount: posts.length,
          padding: const EdgeInsets.all(15.0),
          itemBuilder: (context, position) {
            return Column(
              children: <Widget>[
                Divider(height: 5.0),
                ListTile(
                  title: Text('${posts[position].title}'),
                  subtitle: Text('${posts[position].body}'),
                  leading: ...,
                  onTap: () => _onTapItem(context, posts[position]),
                ),
              ],
            );
          }),
    );
  }

  void _onTapItem(BuildContext context, Post post) { ... }
}

Practice

Add http package

Add this package to the dependencies section of our pubspec.yaml:

dependencies:
  http: "^0.11.3+16"

Create DataModel Class

lib/post.dart

class Post {
  final int userId;
  final int id;
  final String title;
  final String body;

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

  factory Post.fromJson(Map json) {
    return Post(
      userId: json['userId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
    );
  }
}

Create Main App Widget

lib/main.dart

import 'package:flutter/material.dart';
import 'home.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appTitle = 'JavaSampleApproach HTTP-JSON';

    return MaterialApp(
      title: appTitle,
      home: HomePage(title: appTitle),
    );
  }
}

Create Widget for fetching and parsing data

lib/home.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'post.dart';
import 'listposts.dart';

Future> fetchPosts(http.Client client) async {
  final response = await client.get('https://jsonplaceholder.typicode.com/posts');

  return compute(parsePosts, response.body);
}

List parsePosts(String responseBody) {
  final parsed = json.decode(responseBody).cast>();

  return parsed.map((json) => Post.fromJson(json)).toList();
}

class HomePage extends StatelessWidget {
  final String title;

  HomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: FutureBuilder>(
        future: fetchPosts(http.Client()),
        builder: (context, snapshot) {
          if (snapshot.hasError) print(snapshot.error);

          return snapshot.hasData
              ? ListViewPosts(posts: snapshot.data)
              : Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

Create ListView Widget

lib/listpost.dart

import 'package:flutter/material.dart';
import 'post.dart';

class ListViewPosts extends StatelessWidget {
  final List posts;

  ListViewPosts({Key key, this.posts}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView.builder(
          itemCount: posts.length,
          padding: const EdgeInsets.all(15.0),
          itemBuilder: (context, position) {
            return Column(
              children: [
                Divider(height: 5.0),
                ListTile(
                  title: Text(
                    '${posts[position].title}',
                    style: TextStyle(
                      fontSize: 22.0,
                      color: Colors.deepOrangeAccent,
                    ),
                  ),
                  subtitle: Text(
                    '${posts[position].body}',
                    style: new TextStyle(
                      fontSize: 18.0,
                      fontStyle: FontStyle.italic,
                    ),
                  ),
                  leading: Column(
                    children: [
                      CircleAvatar(
                        backgroundColor: Colors.blueAccent,
                        radius: 35.0,
                        child: Text(
                          'User ${posts[position].userId}',
                          style: TextStyle(
                            fontSize: 22.0,
                            color: Colors.white,
                          ),
                        ),
                      )
                    ],
                  ),
                  onTap: () => _onTapItem(context, posts[position]),
                ),
              ],
            );
          }),
    );
  }

  void _onTapItem(BuildContext context, Post post) {
    Scaffold
        .of(context)
        .showSnackBar(new SnackBar(content: new Text(post.id.toString() + ' - ' + post.title)));
  }
}

Source Code

http_example

0 0 votes
Article Rating
Subscribe
Notify of
guest
600 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments