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.
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(Mapjson) { 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(Mapjson) { 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
Create ListView Widget
lib/listpost.dart
import 'package:flutter/material.dart'; import 'post.dart'; class ListViewPosts extends StatelessWidget { final Listposts; 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))); } }