Hero image

Making HTTP requests in Flutter

Mar 09, 2024
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 and json_serializable: for JSON serialisation.
  • build_runner: is the standard Dart package for code generation which is used by mockito and json_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:

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:

  1. makes a POST request to the /posts sending the content type header and the post to create as a JSON encoded string.
  2. checks that the response status code is successful (200) if not an exception is thrown.
  3. 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:

  1. setup the PlaceholderApiClient to use the mock client
  2. setup the mock client to return a JSON-encoded Post when a post request is made with the matching arguments.
  3. call the createPost function.
  4. 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'});
  });
}