Flutter アプリ で OpenApi Generator を使った開発を行う

## これは何？

Flutter アプリで OpenAPI Generator を使った開発する方法をまとめたもの

## Open API Generator とは

OpenAPI Generator とは、OpenAPI Spec を元に、APIクライアントライブラリ（SDK生成）、サーバースタブ、ドキュメント、設定を自動的に生成することができるツールのことです。

これを使うこと以下のようなメリットがあります

- ドキュメントと実装が乖離しづらい
- 呼び元と呼び先の両方で使うデータの定義を自動生成できる

## 自動生成してみる

[サンプルリポジトリ](https://github.com/decoch/open-api-generator-example)

以下のようなディレクトリ構成でサンプルを作成しました

```
open-api-generator-example/
　├ client-todo-api/ # OpenAPI Generator で自動生成されたファイル群
　└ client/ # 開発をするFlutterアプリ
```

まずは自動生成のために使う API の定義を記述します

※ 環境: Flutter / 通信ライブラリに Dio を使用
※ Todoアプリをサンプルとして扱います

```yaml
openapi: 3.0.0
servers:
  - url: "http://localhost:8080/v1"
info:
  version: 1.0.0
  title: OpenAPI Example
paths:
  /todos:
    post:
      summary: Add a new todo
      description: ""
      operationId: addTodo
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Todo"
      requestBody:
        $ref: "#/components/requestBodies/Todo"
    put:
      summary: Update an existing todo
      description: ""
      operationId: updateTodo
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Todo"
      requestBody:
        $ref: "#/components/requestBodies/Todo"
    get:
      summary: Finds todos
      operationId: findTodos
      responses:
        "200":
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Todo"
externalDocs:
  description: Find out more about Swagger
  url: "http://swagger.io"
components:
  requestBodies:
    Todo:
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Todo"
      description: Todo object that needs to be added to the store
      required: true
  schemas:
    Todo:
      title: a Todo
      description: A todo
      type: object
      required:
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
          example: buy milk
        status:
          type: string
          description: todo status
          deprecated: true
          enum:
            - todo
            - doing
            - done
    ApiResponse:
      title: An uploaded response
      description: Describes the result of uploading an image resource
      type: object
      properties:
        code:
          type: integer
          format: int32
        type:
          type: string
        message:
          type: string
```

このまま自動生成すると `pub name` が `openapi` になります。
これだと複数APIあった時にどのAPIかわからなくなってしまうので `client-config.yml` を作成し `todo_api` に変更をします。

```yaml
additionalProperties:
  pubName: todo_api
```

これで設定は完了です。
以下のコマンドで自動生成をします。

```bash
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/todo.yaml -g dart-dio-next -o /local/client-todo-api -c /local/client-config.yml
```

`client-todo-api` [ディレクトリ](https://github.com/decoch/open-api-generator-example/tree/main/client-todo-api)にコードが自動生成されました。

自動生成されたコードは以下のように呼び出すことができます。

```dart
final response = await TodoApi().getDefaultApi().addTodo(todo: todo);
eturn response.data;
```


## アプリに組み込む

`pubspec.yaml` を編集しアプリに組み込みます。

```yml
...
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  todo_api:
    path: ../client-todo-api # 作成したディレクトリを相対パスで指定
```

これでアプリから自動生成したファイルを呼び出すことができるようになりました。

単純なAPIの呼び出しや、

```dart
import 'package:todo_api/todo_api.dart';

Future<Todo?> saveTodos(Todo todo) async {
  final response = await TodoApi().getDefaultApi().addTodo(todo: todo);
  return response.data;
}

Future<List<Todo>> fetchTodos() async {
  final response = await TodoApi().getDefaultApi().findTodos();
  return response.data?.toList() ?? [];
}
```

自動生成されたクラスのインスタンスの生成/変更ができます。

```dart
// 生成
final todo = Todo(
  (b) => b
    ..id = 1
    ..name = 'name',
);

// 変更
final newTodo = todo.rebuild((b) => b..name = 'edited');
```

## まとめ

OpenAPI Generator を使うことで API定義から Flutter アプリから呼び出すことのできるコードを自動生成することができました。

- ドキュメントと実装が乖離しづらい
- 呼び元と呼び先の両方で使うデータの定義を自動生成できる

というメリットがあるので、新規でアプリ開発をする場合は積極的に導入しようと思いました。