Fetching data from the internet is necessary for most apps. In this project JSON data is fetched from an API. In this way we made a simple app that shows NEWS directly from the website. Luckily, Dart and Flutter provide tools, such as the http package, for this type of work.
This recipe uses the following steps:
- Add the
httppackage. - Make a network request using the
httppackage. - Convert the response into a custom Dart object.
- Fetch and display the data with Flutter.
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: <latest_version>
Import the http package.
import 'package:http/http.dart' as http;
This recipe covers how to fetch a sample album from the JSONPlaceholder using the http.get() method.
Future<http.Response> fetchAlbum() {
return http.get('https://jsonplaceholder.typicode.com/albums/1');
}
The http.get() method returns a Future that contains a Response.
Futureis 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.Responseclass contains the data received from a successful http call.
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.
First, create an Album class that contains the data from the network request. It includes a factory constructor that creates an Album from JSON.
Converting JSON by hand is only one option. For more information, see the full article on JSON and serialization.
class Album {
final int userId;
final int id;
final String title;
Album({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
Now, use the following steps to update the fetchAlbum() function to return a Future<Album>:
- Convert the response body into a JSON
Mapwith thedart:convertpackage. - If the server does return an OK response with a status code of 200, then convert the JSON
Mapinto anAlbumusing thefromJson()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 insnapshot, as shown below.)
Future<Album> fetchAlbum() async {
final response = await http.get('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(json.decode(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 fetch() 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. See State for more details.
class _MyAppState extends State<MyApp> {
Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
This Future is used in the next step.
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:
- The
Futureyou want to work with. In this case, the future returned from thefetchAlbum()function. - A
builderfunction that tells Flutter what to render, depending on the state of theFuture: loading, success, or error.
Note that snapshot.hasData only returns true when the snapshot contains a non-null data value. This is why the fetchAlbum function should throw an exception even in the case of a “404 Not Found” server response. If fetchAlbum returns null then the spinner displays indefinitely.
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 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.
For information on how to test this functionality, see the following recipes:
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('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(json.decode(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({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
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: 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 CircularProgressIndicator();
},
),
),
),
);
}
}
dependencies:
http: ^0.12.0import 'dart:convert' show json;
import 'package:http/http.dart' as http;
http.get(API_URL).then((http.Response res) {
final data = json.decode(res.body);
print(data);
});Future<int> doSmthAsync() async {
final result = await Future.value(42);
return result;
}
class SomeClass {
method() async {
final result = await Future.value(42);
return result;
}
}import 'dart:convert' show json;
json.decode(someString);
json.encode(encodableObject);
json.decode returns a dynamic type, which is probably not very useful
You should describe each entity as a Dart class with fromJson and toJson methods
class User {
String displayName;
String photoUrl;
User({this.displayName, this.photoUrl});
User.fromJson(Map<String, dynamic> json)
: displayName = json['displayName'],
photoUrl = json['photoUrl'];
Map<String, dynamic> toJson() {
return {
'displayName': displayName,
'photoUrl': photoUrl,
};
}
}
final user = User.fromJson(json.decode(jsonString));
json.encode(user.toJson());
However this approach is error-prone (e.g. you can forget to update map key after class field was renamed), so you can use json_serializable as an alternative
Add json_annotation, build_runner and json_serializable to dependencies
dependencies:
json_annotation: ^2.0.0
dev_dependencies:
build_runner: ^1.0.0
json_serializable: ^2.0.0
Update your code
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
String displayName;
String photoUrl;
User({this.displayName this.photoUrl});
// _$UserFromJson is generated and available in user.g.dart
factory User.fromJson(Map<String, dynamic> json) {
return _$UserFromJson(json);
}
// _$UserToJson is generated and available in user.g.dart
Map<String, dynamic> toJson() => _$UserToJson(this);
}
final user = User.fromJson(json.decode(jsonString));
json.encode(user); // toJson is called by encode
Run flutter packages pub run build_runner build to generate serialization/deserialization code
To watch for changes run flutter packages pub run build_runner watch

