Flutter+FastAPIでWebアプリを実装

初心者がぱっとわかるような記事がない(気がする)ので、最低限だけ記述。
## 読者対象
* Flutterは環境構築済み(このあたり<cite>https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/06_chapter1_environment</cite>)の人。
* pythonでのバックエンド実装に興味がある人。

## FastAPIとは？
Pythonで実装できる高速&お手軽なWebAPIフレームワーク。

https://fastapi.tiangolo.com/ja/

## バックエンド実装
まずはFastAPIとuvicornのインストール。uvicornはpythonのwebサーバーとwebアプリが通信するためのインターフェース。
(参考：執筆時点でのfastapi、uvicornのバージョンは0.96.0、0.22.0)
```
pip install fastAPI uvicorn
```
任意の場所にsample_api.pyファイルを作成し、以下をコピペ。
```sample_api.py
from fastapi import FastAPI

#インスタンス作成
app = FastAPI()

#GETメソッド(要はデータ取得)の実装。ルートurlにアクセスされたときの処理。
#ほかのメソッドもデコレータを変えるだけで実装可能。
@app.get("/")
def read_root():
    return {"greeting": "Hello World"}
```
ターミナルを起動後、sample_api.pyと同じディレクトリに移動し、以下のコマンドを実行
```
uvicorn sample_api:app --reload --port 8080
```
別のターミナルを起動し、以下を実行
```
curl http://127.0.0.1:8080/
```
すると、以下のdictが出力されるはず。
```
{"greeting":"Hello World"}
```
これでひとまずシンプルなGETメソッド=urlをたたくとdictが返ってくる機構、が実装できた。

## フロントエンド実装
まずは環境構築時に作成したのと同じカウントアプリを作成する。
VSCodeのコマンドパレットから、"Flutter: New Project"を実行、"Application"を選択し、新たなアプリケーションを作成。
VSCodeの実行から"デバッグの開始"を選択すると、以下のアプリが立ち上がる。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470374/c1d7b6e1-a4f3-6c9e-1276-7ede8b7f783b.png" width="500">

↑が立ち上がったのを確認したら、pubsec.yamlのdependenciesに以下を追加し、上書き保存する。
```pubsec.yaml
dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.6 #これを追加。インデントに注意
```
上書き保存すると、httpライブラリがインストールされる(はず)。
インストールされない場合は、コンソールで、flutter pub getを実行。

main.dartの中身を以下のようにすべて書き換える。
詳細な中身はコメント文を参照。
```main.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Center(
          child: ElevatedButton(
            child: const Text("push me and get greeting"),
            onPressed: () async {
              //リクエスト先のurl
              Uri url = Uri.parse("http://127.0.0.1:8080/");

              try {
                //リクエストを投げる
                var res = await http.get(url);

                //リクエスト結果をコンソール出力
                debugPrint(res.body);
              } catch (e) {
                //リクエストに失敗した場合は"error"と表示
                debugPrint("error");
              }
            },
          ),
        ));
  }
}


```
これを上書き保存すると以下のようなアプリに代わっている。
ボタンを押すと指定したurlにリクエストが投げられる。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470374/5470bf4d-535a-4588-8f3d-0abf8b5f6267.png" width="500">

しかし、ボタンを押してみると、コンソールには、
```
error
```
と表示される。あれ？リクエストに失敗している？
## CORSの設定
実は、内部でCORS(クロスオリジンリソース共有)ポリシーにより制限がかかり、httpリクエストはエラーとなってしまう。
これを修正するためには、バックエンド側でリクエストを許可してやる必要がある。

Flutterアプリを一回終了し、VSCodeの"実行とデバッグ(Ctrl+Shift+D)"からlaunch.jsonファイルを作成し、中身を以下のように変更する。configurationsの"name"はアプリ名によって異なるため、そのままでよい。

```
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "flutter_application_1",
            "request": "launch",
            "type": "dart",
            "args": [
                "--web-port",
                "8000"
            ]
        },
        {
            "name": "flutter_application_1 (profile mode)",
            "request": "launch",
            "type": "dart",
            "flutterMode": "profile"
        },
        {
            "name": "flutter_application_1 (release mode)",
            "request": "launch",
            "type": "dart",
            "flutterMode": "release"
        }
    ],
}
```
追加しているのは
```
            "args": [
                "--web-port",
                "8000"
            ]
```
この部分。Flutterアプリを起動する際に、ポート番号を8000に指定している。
この状態でアプリを起動すると、
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470374/8a688b56-2049-1765-16fd-8372c4eb580e.png" width="500">
見えづらいが、ポート番号が8000になっている。

次にバックエンド側でポート番号8000からのリクエストを許可してやる。
sample_api.pyを以下のように変更する(詳細はhttps://fastapi.tiangolo.com/ja/tutorial/cors/ )。
```sample_api.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost:8000",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/")
def read_root():
    return {"greeting": "Hello"}

```
この状態でFlutterアプリでボタンを押すと、コンソールに
```
{"greeting":"Hello"}
```
と出力される。

## 最後に
ひとまずこれでFlutter+pythonでそれぞれフロントエンドとバックエンド間の通信を実装できた。

## 参考
・FastAPIの構築

https://qiita.com/yota_dev/items/ab8dea7f71c8a130d5bf

・FastAPIのCORS制御

https://fastapi.tiangolo.com/ja/tutorial/cors/

・Flutterのポート番号指定

https://qiita.com/agajo/items/b3a7afa07040c0f7132c