Making HTTP requests in Flutter
In this article, we’re going to talk about creating an HTTP client in Flutter/Dart. We’ll use this placeholder API https://jsonplaceholder.typicode.com/ as a stand-in for an API of our own. The placeholder API has a /posts
endpoint that is used to create a post (blog article) by sending a POST
request to it.
Check out a complete example on GitHub: minibuildsio/flutter_http_client_example.
Dependencies
We need to add the following regular and dev dependencies:
http
: for making HTTP requests.mockito
: for generating mocks for testing.equatable
: a convenient way of creating override ‘==’.json_annotation
andjson_serializable
: for JSON serialisation.build_runner
: is the standard Dart package for code generation which is used bymockito
andjson_serializable
to generate mocks and helper functions.
dependencies:
http: ^1.2.1
json_annotation: ^4.8.1
mockito: ^5.4.4
equatable: ^2.0.5
dev_dependencies:
json_serializable: ^6.7.1
build_runner: ^2.4.8
Placeholder API client
The PlaceholderApiClient
class encapsulates all interactions with the HTTP API. It’ll handle making HTTP requests, serialising/deserialising requests/responses, and checking for errors.
class PlaceholderApiClient {
final String apiUrl;
final Client httpClient;
PlaceholderApiClient(this.apiUrl, this.httpClient);
Future<Post> createPost(int userId, String title, String body) async {
// TODO: make a POST request to /posts passing the userId, title, and body
}
}
Its constructor takes the following parameters:
- apiUrl: The base URL of the API endpoint e.g. https://jsonplaceholder.typicode.com/.
- httpClient: An HTTP client to perform GET, POST, etc requests.
Having these as dependencies provided via the constructor allows us to provide mock values/implementations which is very handy for testing. For example, instead of using a real implementation of Client
which would make a real HTTP request, we can provide a mock one that simulates HTTP requests.
It also has a method createPost(...)
which will make an HTTP request to the API passing the provided user id, title, and body.
Making requests using the http package
The Client
class in the http
package provides functions get(...)
, post(...)
, etc for making HTTP requests. The methods take a URL and optionally some headers and a request body. The HTTP functions in the http
package are asynchronous operations that is instead of returning a result immediately they return a Future which represents a value to be returned in the future after the operation has been completed. To be the value after completion we can use the await
keyword.
Below is the body of the createPost
function it performs the following:
- makes a POST request to the
/posts
sending the content type header and the post to create as a JSON encoded string. - checks that the response status code is successful (200) if not an exception is thrown.
- parses the body into an object.
Future<Post> createPost(int userId, String title, String body) async {
var url = Uri.parse('$apiUrl/posts');
var response = await httpClient.post( // [1]
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'userId': userId,
'title': title,
'body': body,
}),
);
if (response.statusCode != 200) { // [2]
throw Exception('Failed to create post ${response.body}');
}
return Post.fromJson(jsonDecode(response.body) as Map<String, dynamic>); // [3]
}
The Post
class is just a simple data class annotated with @JsonSerializable
to generate the JSON helper functions, here’s an article about JSON serialisation. We’re extending Equatable
so that ==
compares the fields of the class, see here for the full class.
@JsonSerializable()
class Post extends Equatable {
final int id;
final int userId;
final String title;
final String body;
// omitted for brevity
}
Testing the client
To test the client we’re going to use mockito
to create a mock client this will allow us to simulate the POST
request instead of making the real HTTP request. To get mockito
to generate a mock version of a class use the @GenerateMocks
annotation providing the class to generate a mock for.
@GenerateMocks([Client])
void main() {
// the tests...
}
mockito
uses code generation to create the mock version of the client so we need to run the build_runner
like so:
dart run build_runner build
The test is fairly straightforward we want to make a request and check the request and response. The steps are:
- setup the
PlaceholderApiClient
to use the mock client - setup the mock client to return a JSON-encoded
Post
when a post request is made with the matching arguments. - call the
createPost
function. - check that the response was as expected and the request was made correctly.
@GenerateMocks([Client])
void main() {
test('create post makes POST request to /posts', () async {
var client = MockClient(); // [1]
final placeholderClient = PlaceholderApiClient('https://jsonplaceholder.typicode.com', client);
when( // [2]
client.post(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {'Content-Type': 'application/json'},
body: anyNamed('body'),
),
).thenAnswer((_) async => Response("""
{ "id": 100, "userId": 10, "title": "a-title", "body": "a-body" }
""", 200));
final post = await placeholderClient.createPost(10, 'a-title', 'a-body'); // [3]
expect(post, const Post(100, 10, 'a-title', 'a-body')); // [4]
final verification = verify(client.post(
any,
headers: anyNamed('headers'),
body: captureAnyNamed('body'),
));
final requestBody = jsonDecode(verification.captured.first as String);
expect(requestBody, {'userId': 10, 'title': 'a-title', 'body': 'a-body'});
});
}