# Call the API endpoints - the Chopper HTTP client

# Intro

This is the second part of [the blog post series](https://blog.dtengeri.com/series/tinder-for-cats-dogs) that aims to implement the February Challenge of [flutterchallenge.dev](https://flutterchallenge.dev) the [Tinder for Cats and Dogs](https://flutterchallenge.dev/tinder-for-cats-and-dogs).

This post shows an implementation of the communication layer of our application by using the [chopper](https://pub.dev/packages/chopper) package.

You can find the source code at https://github.com/dtengeri/tinder_cat_dog_app

# What is [chopper](https://pub.dev/packages/chopper)?

What does the [chopper](https://pub.dev/packages/chopper) package provide us and why it is useful?

[chopper](https://pub.dev/packages/chopper) is an HTTP client generator. It generates source code for you, to manage HTTP calls in your app. It generates the boring stuff, you just need to define the methods and annotate them with some meta information, chopper will translate it to HTTP calls and handles JSON serialization, HTTP headers and every HTTP related configuration for you.

# Add chopper to your app

Let's add [chopper](https://pub.dev/packages/chopper) to the Tinder for Cats and Dogs application.

Just for a remainder, the Tinder for Cats and Dogs was the first challenge of [flutterchallenge.dev](https://flutterchallenge.dev). It provides a [REST API](https://tinder-cat-dog-api.herokuapp.com/swagger.html) to get the list of available cats and dogs and to store the user's votes.
![Screenshot 2022-04-03 at 8.53.14.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1648968833615/C5iiJPDfc.png)

Let's start with adding the required dependencies to our application.
```yaml
dependencies:
  chopper: ^4.0.5

dev_dependencies:
  build_runner: ^2.1.11
  chopper_generator: ^4.0.5
```

Since [chopper](https://pub.dev/packages/chopper) is a code generator, we are going to use the [build_runner](https://pub.dev/packages/build_runner) to generate the code the same way as for [freezed](https://pub.dev/packages/freezed) in the [first part](https://blog.dtengeri.com/represent-the-data-coming-from-a-rest-api-with-freezed) of the [series](https://blog.dtengeri.com/series/tinder-for-cats-dogs).

## Chopper basics

[chopper](https://pub.dev/packages/chopper) main class is the `ChopperClient` which does the communication with our backend service. It relies on services. A service is a `ChopperService`, which is the generated code for the endpoints. Since we are using [freezed](https://pub.dev/packages/freezed) it provides us JSON serialization as well. We can create a custom converter for [chopper](https://pub.dev/packages/chopper) so it can automatically convert the request and response body from JSON to our models.

## Create the first service

Let's start with creating our first service, for the `/cats` endpoint.
```dart
import 'package:chopper/chopper.dart';
import 'package:tinder_cat_dog_app/features/animals/animals.dart';

// The place of the generated code.
part 'cat_list_service.chopper.dart';

// This annotation tells chopper that this service
// should send its requests to the /cats path.
@ChopperApi(baseUrl: '/cats')
abstract class CatListService extends ChopperService {
  // This method creates the service based on the generated code.
  static CatListService create([ChopperClient? client]) =>
      _$CatListService(client);

  // This tells chopper to generate a method that wil send a GET request
  // to the /cats endpoint and returns a list of Cat models.
  @Get()
  Future<Response<List<Cat>>> getCats();
}
```
[chopper](https://pub.dev/packages/chopper) provides annotations that can be used to aid the code generation. In the `CatListService` we define a `getCats()` method that will send a `GET` request to our REST API's `/cats` endpoint. The endpoint's response is in JSON format
```json
[
  {
    "id": "9eef0518d8b5a333ee1fcb1a06a1474a23192c6a",
    "name": "Luna",
    "path": "/cats/cat001.jpeg"
  }
]
```
The generated code processes this JSON string and converts its content to our `Cat` model and adds all cats to a list. And all of this happens by just adding the `@Get()` annotation.

Run 
```bash
flutter pub run build_runner build  --delete-conflicting-outputs
``` 
to generate the service or watch your file changes and generate the [chopper](https://pub.dev/packages/chopper) services automatically by
```bash
flutter pub run build_runner watch  --delete-conflicting-outputs
```

## Processing JSON strings

We can provide converters to `ChopperClient` that could process JSON strings and convert them to our own models.
```dart
import 'package:chopper/chopper.dart';
import 'package:tinder_cat_dog_app/features/core/core.dart';

/// This function gets a JSON map and returns a model of type [T].
typedef JsonFactory<T> = T Function(Map<String, dynamic> json);

/// A [JsonConverter] for chopper that uses converts our models to and 
/// from JSON.
class JsonSerializableConverter extends JsonConverter {
  /// Creates a new [JsonSerializableConverter] with the given [factories].
  const JsonSerializableConverter(this.factories);
  /// Collection of the factories of our model [Type]s. 
  final Map<Type, JsonFactory> factories;

  T _decodeMap<T>(Map<String, dynamic> values) {
    /// Get jsonFactory using Type parameters
    /// if not found or invalid, throw error or return null
    final jsonFactory = factories[T];
    if (jsonFactory == null || jsonFactory is! JsonFactory<T>) {
      /// throw serializer not found error;
      throw ArgumentError('Serializer not found');
    }

    return jsonFactory(values);
  }

  List<T> _decodeList<T>(List values) =>
      values.where((v) => v != null).map<T>((v) => _decode<T>(v)).toList();

  dynamic _decode<T>(entity) {
    if (entity is Iterable) return _decodeList<T>(entity.toList());

    if (entity is Map<String, dynamic>) return _decodeMap<T>(entity);

    return entity;
  }

  /// Converts a reponse coming from the server to [ResultType].
  @override
  Response<ResultType> convertResponse<ResultType, Item>(Response response) {
    final jsonRes = super.convertResponse(response);

    return jsonRes.copyWith<ResultType>(body: _decode<Item>(jsonRes.body));
  }

  /// Converts the request body if it implements [ToJsonConvertable].
  /// 
  /// It calls [ToJsonConvertable.toJson] method on the body to convert
  /// the body to JSON string.
  @override
  Request convertRequest(Request request) {
    final convertedRequest = super.convertRequest(request);
    if (request.body is ToJsonConvertable) {
      return convertedRequest.copyWith(
        body: (request.body as ToJsonConvertable).toJson(),
      );
    }
    return convertedRequest;
  }

  @override
  Response convertError<ResultType, Item>(Response response) {
    final jsonRes = super.convertError(response);

    return jsonRes;
  }
}
```
There are 2 key parts in the converter.
1. We have to provide a map for all the types that our chopper client can handle through the REST APIs. This map contains the factory functions that could convert a JSON map to the given type. Such a function is `Cat.fromJson`, which is generated by [freezed](https://pub.dev/packages/freezed).
2. In the `convertRequest` method we are checking the type of the request body. If it implements `ToJsonConvertable` and has a `toJson()` method, it will use it to convert the data class to JSON. This is true for our models generated by [freezed](https://pub.dev/packages/freezed). We only have to extend or implement the `ToJsonConvertable` class.

```dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:tinder_cat_dog_app/features/core/core.dart';

part 'vote_param.freezed.dart';
part 'vote_param.g.dart';

/// Extend [ToJsonConvertable] so [VoteParam] can be used
/// as a request body and it will be converted automatically to JSON.
@freezed
class VoteParam extends ToJsonConvertable with _$VoteParam {
  const factory VoteParam({
    @JsonKey(name: 'vote_type') required String voteType,
    @JsonKey(name: 'animal_id') required String animalId,
    required bool liked,
  }) = _VoteParam;

  factory VoteParam.fromJson(Map<String, dynamic> json) =>
      _$VoteParamFromJson(json);
}
```
The `ToJsonConvertable` is an abstract class with the definition of `toJson()`.

```dart
abstract class ToJsonConvertable {
  Map<String, dynamic> toJson();
}
```

## Configure the requests

Let's see a more complex service that uses variables in the URLs, sends data as the request body and uses Authorization headers.

```dart
import 'package:chopper/chopper.dart';
import 'package:tinder_cat_dog_app/features/animals/animals.dart';

part 'vote_service.chopper.dart';

@ChopperApi(baseUrl: '/votes')
abstract class VoteService extends ChopperService {
  static VoteService create([ChopperClient? client]) => _$VoteService(client);

  // We can provide values for HTTP headers like authorization token
  // with the @Header annotation.
  @Get()
  Future<Response<List<Vote>>> getVotes({
    @Header('Authorization') required String token,
  });

  // We can use variables in the URL by using {variable-name} in the
  // @Get() annotation and marking a method parameter with @Path().
  @Get(path: '/{id}')
  Future<Response<Vote>> getVote({
    @Path() required String id,
    @Header('Authorization') required String token,
  });

  // We can provide the body of a POST request by annotate a method parameter
  // with @Body().
  @Post()
  Future<Response<Vote>> createVote({
    @Body() required VoteParam voteParam,
    @Header('Authorization') required String token,
  });

  @Put(path: '/{id}')
  Future<Response<Vote>> updateVote({
    @Path() required String id,
    @Body() required VoteParam voteParam,
    @Header('Authorization') required String token,
  });

  @Delete(path: '/{id}')
  Future<Response> deleteVote({
    @Path() required String id,
    @Header('Authorization') required String token,
  });
}
```

We can use variables in the URL, which will be replaced by one of the method parameters.
```dart
@Get(path: '/{id}')
  Future<Response<Vote>> getVote({
    @Path() required String id,
    @Header('Authorization') required String token,
  });
```
The `{id}` in the path creates a placeholder in the URL and the `@Path()` annotation before the `id` method parameter connects it to this placeholder.

We can provide HTTP headers as well by using `@Header()`. We can use it for example to set a JWT token in the `Authorization` header.

## Create the `ChopperClient`

Let's configure the `ChopperClient` with our generated services and converter, so we can use it in our app. We're going to use a factory called `ChopperClientFactory`.

```dart
import 'package:chopper/chopper.dart';
import 'package:http/http.dart';
import 'package:tinder_cat_dog_app/features/animals/animals.dart';
import 'package:tinder_cat_dog_app/features/core/core.dart';

class ChopperClientFactory {
  /// Creates a new [ChopperClient] with our services and converter.
  static ChopperClient create([Client? client]) => ChopperClient(
        client: client,
        baseUrl: 'https://tinder-cat-dog-api.herokuapp.com',
        services: [
          // These are the services that we made and chopper
          // generated their logic.
          AuthService.create(),
          CatListService.create(),
          DogListService.create(),
          VoteService.create(),
        ],
        // This is our custom converter.
        converter: const JsonSerializableConverter({
          Cat: Cat.fromJson,
          Dog: Dog.fromJson,
          Vote: Vote.fromJson,
        }),
      );
}
```

## Use our client

It's time to use our client to make HTTP requests.
Let's create a new instance of it
```dart
final chopper = ChopperClientFactory.create();
```
Get the list of cats
```dart
final catListService = chopper.getService<CatListService>();
final response = await catListService.getCats();
if (response.statusCode == 200 && response.body != null) {
  for (Cat cat in response.body!) {
    // Do something with the cat.
  }
}
```
Create a vote
```dart
final voteService = chopper.getService<VoteService>();
final response = await voteService.createVote(
  token: 'Bearer xxxx',
  voteParam: const VoteParam(voteType: 'cat', animalId: '1', liked: true),
);
```
# Summary

In this post, we created the communication layer of our application that uses the REST API and our data classes. The [chopper](https://pub.dev/packages/chopper) package provides code generation features that help us quickly create services that communicate with our API. It generates the code for making HTTP requests. Our task is to annotate our methods and parameters and the rest is done by [chopper](https://pub.dev/packages/chopper).

# Resources

You can find the source code of the application at https://github.com/dtengeri/tinder_cat_dog_app
