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



By grokonez | July 8, 2018.

Last updated on May 6, 2021.



Related Posts


7 thoughts on “Flutter HTTP Client example with ListView – Fetch data and parse JSON in background”

  1. thanks for this article, helped me get started with my project.
    One think that I ran into and could not figure out was how to add http client timeout and show message for it.
    Is that something you have an example of ?

    1. Hi Peder,

      We have 2 ways to set Timeout for HTTP Request:
      – use Future.timeout() method:

      try {
        final request = await client.get(...);
        final response = await request.close()
          .timeout(const Duration(seconds: 2));
        // rest of the code
        ...
      } on TimeoutException catch (_) {
        // A timeout occurred.
      } on SocketException catch (_) {
        // Other exception
      }
      

      – use HttpClient.connectionTimeout() method => apply to all requests made by the same client. When a request exceeds this timeout, a SocketException is thrown:

      final client = new HttpClient();
      client.connectionTimeout = const Duration(seconds: 5);
      

      Regards,
      grokonez.

  2. First of all your tutorials are great learning source. Specially flutter.

    Regarding this tutorial, how can we add pagination call in this code with scrollcontroller so that when scroll reach to end then we can pass next url.

    You are doing great work.

    Thanks

  3. The example that I am going to show you next is a simple screen with a button at the bottom, by pressing the button we will get the data from a REST service and we parsed the JSON to a custom object, we will show a widget while the operation is in progress and finally we show the result

Got Something To Say:

Your email address will not be published. Required fields are marked *

*