Fetch data from the internet
Fetching data from the internet is necessary for most apps. Luckily, Dart and Flutter provide tools, such as the http package, for this type of work.
- Add the http package
- Make a network request
-
Future is a core Dart class for working with async operations. A Future object represents a potential value or error that will be available at some time in the future.
-
The http.Response class contains the data received from a successful http call.
- Convert the response into a custom Dart object
-
Convert the response body into a JSON Map with the dart:convert package
-
If the server does return an OK response with a status code of 200, then convert the JSON Map into an Album using the fromJson() factory method.
-
If the server does not return an OK response with a status code of 200, then throw an exception. (Even in the case of a “404 Not Found” server response, throw an exception. Do not return null. This is important when examining the data in snapshot, as shown below.)
- Fetch the data
- Display the data
-
The Future you want to work with. In this case, the future returned from the fetchAlbum() function.
-
A builder function that tells Flutter what to render, depending on the state of the Future: loading, success, or error.
- Why is fetchAlbum() called in initState()?
- Complete example
The http package provides the simplest way to fetch data from the internet.
To install the http package, add it to the dependencies section of the pubspec.yaml file. You
can find the
latest version of the http package the
pub.dev.
dependencies:
http: ^0.13.3
Import the http package.
import 'package:http/http.dart' as http;
Additionally, in your AndroidManifest.xml file, add the Internet permission.
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
This recipe covers how to fetch a sample album from the JSONPlaceholder using the http.get() method.
Future<http.Response> fetchAlbum() {
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
}
The http.get() method returns a Future that contains a Response.
While it’s easy to make a network request, working with a raw Future<http.Response> isn’t very convenient. To make your life easier, convert the http.Response into a Dart object.
Create an Album class
First, create an Album class that contains the data from the network request. It includes a factory constructor that creates an Album from JSON.
class Album {
final int userId;
final int id;
final String title;
Album({
required this.userId,
required this.id,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
Convert the http.Response to an Album
Now, use the following steps to update the fetchAlbum() function to return a Future<Album>:
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
Hooray! Now you’ve got a function that fetches an album from the internet.
Call the fetchAlbum() method in either the initState() or didChangeDependencies() methods. The initState() method is called exactly once and then never again. If you want to have the option of reloading the API in response to an InheritedWidget changing, put the call into the didChangeDependencies() method.
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
To display the data on screen, use the FutureBuilder widget. The FutureBuilder widget comes with Flutter and makes it easy to work with asynchronous data sources. You must provide two parameters:
Note that snapshot.hasData only returns true when the snapshot
contains a
non-null data value.
Because fetchAlbum can only return non-null values, the function should throw
an exception
even
in the case
of a “404 Not Found” server response.
Throwing an exception sets the snapshot.hasError to
true
which can be
used to display an error message.
Otherwise, the spinner will be displayed.
FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
Although it’s convenient, it’s not recommended to put an API call in a build() method.
Flutter calls the build() method every time it needs to change anything in the view, and this
happens
surprisingly often. Leaving the fetch call in your build() method floods the API with
unnecessary calls and
slows down your app.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
Album({
required this.userId,
required this.id,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}