Fletで簡易アプリ作成

# はじめに
Pythonを使用したWebアプリ作成として、StreamlitやGradioを今まで使用してきました。
今回Fletというライブラリの使用感を確かめてみました。

# Fletとは
Flutterが基盤となっているPythonのWebアプリ作成のライブラリです。
Pythonでクロスプラットフォーム(Web、モバイル、デスクトップ)アプリを構築することが可能。

https://flet.dev/


# 環境準備
## ディレクトリ構成
以下のようなディレクトリ構成としています。
```
.
├── app
│   └── main.py
├── Dockerfile
└── requirements.txt
```

## Dockerfile
Dockerを使用して、Fletを動かそうと思います。

```dockerfile:Dockerfile
FROM python:3.12-slim

RUN useradd -m fletuser

RUN chsh -s /bin/bash fletuser

WORKDIR /app

COPY requirements.txt .

RUN chown -R fletuser:fletuser /app

USER fletuser

RUN pip install --no-cache-dir --user -r requirements.txt

ENV PATH="/home/fletuser/.local/bin:$PATH"

COPY ./app /app

EXPOSE 8550

CMD ["flet", "run", "--web", "main.py", "--port", "8550"]

```

## requirements.txt
fletのライブラリを記載する。
```requirements.txt
flet
flet_desktop
```

## main.py
シンプルに`Hello world!`を出力する画面。

```python:main.py
import flet as ft

def main(page: ft.Page):
    page.add(ft.Text(value="Hello world!"))

ft.app(main)
```

## Dockerイメージのビルド
以下コマンドでDockerイメージをビルドする。
```bash
docker build -t flet_test .
```

## Dockerコンテナの起動
以下コマンドでDockerコンテナを起動する。
```bash
docker run -p 8550:8550 flet_test
```

[http://localhost:8550/](http://localhost:8550/)にアクセスすると、`Hello world!`の画面が現れる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3968617/ecd82b96-ec30-c3d1-4497-4f6c999fde1e.png)

# インタラクティブフォームアプリ
## 作成するアプリ
入力フォームに名前を入力したら、定型挨拶を返してくれるアプリを作成したいと思います。

## 全体のコード
```python
import flet as ft


def main(page: ft.Page):
    def btn_click(e):
        if not txt_name.value:
            txt_name.error_text = "名前は必須入力です!"
            page.update()
        else:
            name = txt_name.value
            page.clean()
            page.add(ft.Text(f"こんにちは, {name}さん!"))

    txt_name = ft.TextField(label="名前を入力してください。")

    page.add(txt_name, ft.ElevatedButton("送信", on_click=btn_click))


ft.app(main)
```

## 解説
### ft.TextField
`ft.TextField`でテキストフィールドを定義することができます。
そのフィールドで入力された値は、`txt_name`変数に格納されることになります。

```python
txt_name = ft.TextField(label="名前を入力してください。")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3968617/943a86c5-9a98-867b-3557-9aafee22a7ce.png)

https://flet.dev/docs/controls/textfield/

### ft.ElevatedButton
`ft.ElevatedButton`は、陰で塗りつぶされたトーンボタンを構成できます。
第1引数にコンテンツ名を、`on_click`引数にボタンをクリックしたときの挙動を指定します。

```python
ft.ElevatedButton("送信", on_click=btn_click)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3968617/f1ef7814-a884-b922-8cc0-fdcfeca1e324.png)


https://flet.dev/docs/controls/elevatedbutton

ボタンの種類は様々ありますので、デザインに合ったボタンを使用すると良いと思います。

https://flet.dev/docs/controls/buttons

### btn_click
ボタンをクリックしたときの挙動を`main`の内部関数として定義します。

[ft.TextField](#fttextfield)で定義した`txt_name`の値の有無によって条件分岐させています。

- 無い場合
    - `名前は必須入力です!`と表示させる
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3968617/535798b7-101c-bc81-73f8-05e3c6e38219.png)
- 有る場合
    - `こんにちは, {name}さん!`と表示させる
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3968617/83ca1408-e625-7a2a-84aa-6d40d9be5a0f.png)


```python
def btn_click(e):
    if not txt_name.value:
        txt_name.error_text = "名前は必須入力です!"
        page.update()
    else:
        name = txt_name.value
        page.clean()
        page.add(ft.Text(f"こんにちは, {name}さん!"))
```

# おわりに
今回はPythonでFlutterベースのアプリが作成できるFletのサンプルアプリを作成してみました。
Streamlitと比べると少々癖があると思いますが、同じコードでクロスプラットフォームに対応しているのでメリットもあると思いました。
Fletはまだ新しいフレームワークなので、これから伸びてくる可能性を秘めていると思いました。
Pythonを使用した簡易アプリを作成するときの候補として、知見を持っておこうと思います。