# Represent the data coming from a REST API with freezed

This year in the February challenge at [flutterchallenge.dev](https://flutterchallenge.dev/tinder-for-cats-and-dogs) the goal was to build a Tinder-like application for cats and dogs by using a REST API to fetch data. This is the first part of a series that shows an implementation for this challenge and now we are going to create our data classes to represent the data coming from the REST endpoints with the help of the package [freezed](https://pub.dev/packages/freezed).

`freezed` is a code generator tool that helps you build your data classes with tons of helpful features, like the `copyWith()` or `fromJson()` methods.

## Add the dependencies

So let's start with a fresh new Flutter project and add `freezed` to the dependencies:

```yaml
# pubspec.yaml
dependencies:
  freezed_annotation: ^1.1.0
  json_annotation: ^4.4.0

dev_dependencies:
  build_runner: ^2.1.8
  freezed: ^1.1.1
  json_serializable: ^6.1.5
```
The `freezed_annotations` will help us mark our classes so the `freezed` package will know which classes need to be generated when we run `build_runner`. If we add `json_annotation` and `json_serializable` to the dependencies as well, `freezed` can generate the `toJson()` and `fromJson()` methods as well.

## Create our data representation

Let's see what we the REST API provides us. The challenge provides a [swagger documentation](https://tinder-cat-dog-api.herokuapp.com/swagger.html) about the available endpoints. Our goal is to have a class for each endpoint to represent its JSON response in our app.

![Screenshot 2022-04-03 at 8.53.14.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1648968833615/C5iiJPDfc.png)

### Model the API with `freezed`

Let's start with the `/cats` endpoint which will return an array of cats in the following format
```json
[
  {
    "id": "9eef0518d8b5a333ee1fcb1a06a1474a23192c6a",
    "name": "Luna",
    "path": "/cats/cat001.jpeg"
  }
]
```
So our `Cat` class should have 3 fields. This is how it could be created with the help of `freezed`. Let's create a `cat.dart` file under the folder `lib/features/animals/models`. 
```dart
import 'package:freezed_annotation/freezed_annotation.dart';

// This file contains the generated code by freezed.
part 'cat.freezed.dart';
// This file contains the generated code by json_serializable.
part 'cat.g.dart';

// We mark the class with @freezed annotation so freezed knows it has 
// some to work to do.
// _$Cat is a generated class which contains all the constructors, 
// methods that makes our class immutable.
@freezed
class Cat with _$Cat {
  // With the help of this factory constructor we define the fields 
  // of our class.
  // _Cat is a class that will hold the implementation details. 
  const factory Cat({
    required String id,
    required String? name,
    required String path,
  }) = _Cat;
  // Adding this factory will instruct freezed and json_serializable to 
  // generate the fromJson() and toJson() methods for us.
  factory Cat.fromJson(Map<String, dynamic> json) => _$CatFromJson(json);
}
```
Now we have to run `build_runner` to generate the code so the `cat.freezed.dart` and `cat.g.dart` files are created and all the errors are eliminated from our IDE.

You can generate the code once by running 
```bash
flutter pub run build_runner build --delete-conflicting-outputs
```
But if you are developing an app you can watch for changes and always generate the code without running manually the `build_runner`.
```bash
flutter pub run build_runner watch --delete-conflicting-outputs
```
`build_runner` will scan your codebase and if there is a code generator that finds something to work on, it will execute it.

Let's move on to the next model, to the `Vote`. The `/votes`, and `/votes/{id}` endpoints. Whenever we get, create or modify a vote, the API responses with a structure like this.
```json
{
  "id": 1,
  "user_id": 1,
  "vote_type": "cat",
  "animal_id": "9eef0518d8b5a333ee1fcb1a06a1474a23192c6a",
  "liked": true,
  "created_at": "2022-01-07T11:39:40.992Z",
  "updated_at": "2022-01-07T11:39:40.992Z"
}
``` 
We can do the same as we did with the cats.
```dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'vote.freezed.dart';
part 'vote.g.dart';

@freezed
class Vote with _$Vote {
  const factory Vote({
    required int id,
    required int user_id,
    required String vote_type,
    required String animal_id,
    required bool liked,
    required DateTime created_at,
    required DateTime updated_at,
  }) = _Vote;

  factory Vote.fromJson(Map<String, dynamic> json) => _$VoteFromJson(json);
}
```
In addition to the basic `int` and `String` types, the `json_serialiable` can handle `DateTime` as well.
We have the `part` definitions, the class with the `mixin` and the `factory` method with the fields we want in our class along with the `fromJson`.

### Represent the HTTP request body

When we want to create or modify a vote, the `POST /votes` and `PUT /votes/{id}` endpoints have a request body, which is basically a JSON with specific fields. We can represent this JSON the same way as we did with the data we are getting back from the API. So this
```json
{
  "vote_type": "cat",
  "animal_id": "9eef0518d8b5a333ee1fcb1a06a1474a23192c6a",
  "liked": true
}
```
can be managed by
```dart
import 'package:freezed_annotation/freezed_annotation.dart';

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

@freezed
class VoteParam with _$VoteParam {
  const factory VoteParam({
    required String vote_type,
    required String animal_id,
    required bool liked,
  }) = _VoteParam;

  factory VoteParam.fromJson(Map<String, dynamic> json) =>
      _$VoteParamFromJson(json);
}
```
Following the above, we can represent our data in our app easily. If you don't want to do this manually and you are using VSCode, I can recommend the [Json to Dart Model](https://marketplace.visualstudio.com/items?itemName=hirantha.json-to-dart) extension, which can generate your data classes from a JSON string and it can work with `freezed`. 

### Fix the linting errors

You'll get lint errors for the `Vote` class because of the variable naming
```
Name non-constant identifiers using lowerCamelCase.
```
You can fix this by using the `JsonKey` annotation like this
```dart
// ignore_for_file: invalid_annotation_target

import 'package:freezed_annotation/freezed_annotation.dart';

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

@freezed
class VoteParam 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);
}
```
By default, you'll get another lin error due to this [issue](https://github.com/rrousselGit/freezed/issues/488), you can safely ignore it by putting the following at the top of your file.
```dart
// ignore_for_file: invalid_annotation_target
```

## Summary

By using code generation, you can quickly add the data classes to your project to model a REST API. The good thing is that you'll get lots of features by default that can improve the quality of your projects in the long run and it can speed up your development time since you don't have to write the code by yourself.

You can find the relevant source code [here](https://github.com/dtengeri/tinder_cat_dog_app).

In the next part of the series, I'll introduce the communication with the REST API with the help of the [chopper](https://pub.dev/packages/chopper) package.
