№ 019 / Blog
構造分析ノート · 2026.05.18

Flet で業務ソフトを開発 — 現代版 Visual Basic

気象データ可視化アプリ aiseed-weather を題材に

気象データ可視化アプリ aiseed-weather を題材に

「Python だけで」「宣言的に」「クロスプラットフォームの GUI を書く」 ― Flet はこの三つを本気で実現していて、業務ソフトの開発用 UI として、 Visual Basic 6 が当時持っていた生産性を、現代のスタックで取り戻している。

この記事では抽象論はやめて、実際の中規模アプリ aiseed-weather (気象庁・ECMWF・Open-Meteo を統合した天気図スタジオ、Flet 0.85 + Python 3.13 製、コンポーネント 6,800 行、図形描画 3,200 行)を例に、業務 GUI として Flet が何を解決しているかを見ていく。

※コードは、以後の開発で変更されている場合があります。


アプリの構成

src/aiseed_weather/
├── components/        # Flet コンポーネント (UI)
│   ├── app.py             ← ルート + ナビゲーション
│   ├── point_forecast_view.py  ← 地点予報タブ (2,000 行)
│   ├── map_view.py             ← 天気図タブ (3,300 行)
│   ├── radar_view.py           ← レーダータブ
│   └── amedas_view.py          ← AMeDAS 観測タブ
├── figures/           # matplotlib + flet.canvas でのチャート描画
├── services/          # ECMWF / ERA5 / JMA / Open-Meteo へのアクセス
└── models/            # データクラス / 設定

UI ・データ取得・データ処理がすべて Python で書かれている。 Web/REST API のレイヤがなく、polars の DataFrame をそのまま Flet コンポーネントに渡している。

天気図


1. 宣言的コンポーネント ― React Hooks 風が Python ネイティブ

Flet 0.70+ の宣言的モードでは、@ft.component でデコレートした関数がコンポーネントになり、ft.use_state / ft.use_effect / ft.use_ref といったフックを React 同様に使う。

aiseed-weather の地点予報ビュー (実コードから抜粋):

@ft.component
def PointForecastView(settings: UserSettings):
    data_dir = resolved_data_dir(settings)
    locations, set_locations = ft.use_state(load_locations(data_dir))
    selected_name, set_selected_name = ft.use_state(settings.default_location)

    forecast_state, set_forecast_state = ft.use_state("idle")
    forecast_data, set_forecast_data = ft.use_state(None)
    error_msg, set_error_msg = ft.use_state("")

    variable, set_variable = ft.use_state("temperature_2m")
    visible_days, set_visible_days = ft.use_state(7)
    pan_offset_h, set_pan_offset_h = ft.use_state(0)
    # … (実コードでは 30+ の use_state スロット)

地点リスト、選択中の地点、ロード状態、予報データ、エラーメッセージ、表示変数、表示期間、パンオフセット… 業務 UI のすべての状態を、ローカルな use_state だけで扱えている。Redux も Context Provider も Zustand も登場しない。

    async def fetch_forecast(_):
        set_forecast_state("loading")
        try:
            df = await fetch_open_meteo(lat, lon, ...)
            set_forecast_data(df)
            set_forecast_state("ready")
        except Exception as e:
            set_error_msg(str(e))
            set_forecast_state("error")

    return ft.Column([
        ft.Dropdown(
            value=selected_name,
            options=[ft.DropdownOption(loc.name) for loc in locations],
            on_change=lambda e: set_selected_name(e.value),
        ),
        ft.FilledButton("更新", on_click=fetch_forecast),
        _StatusBanner(forecast_state, error_msg),
        _HourlyStrip(forecast_data) if forecast_data else None,
        _Chart(forecast_data, variable, visible_days, pan_offset_h),
    ])

これが コンポーネントの全貌。VB の Button1_Click がそのまま on_click= に置き換わっていると思えばよい。async def のハンドラがそのまま受理されるところが、現代的な改善点。


2. リアクティブな共有状態 ― @ft.observable

タブをまたいで生きる「長時間ダウンロード処理」のような状態には、 @ft.observable 付きのデータクラスを使う。これは aiseed-weather の天気図タブが、ECMWF GRIB2 ファイル(1 ファイル数十 MB)を S3 から並列ダウンロードする間、進捗を画面上のあらゆる場所に伝播させるための仕組み:

@ft.observable
@dataclass
class FetchSession:
    """ECMWF Open Data ダウンロードのライフサイクル状態。
    タブ移動しても生き続け、すべてのコンポーネントが購読する。"""
    running: bool = False
    items: list = field(default_factory=list)
    progress: dict = field(default_factory=lambda: {"done": 0, "total": 0})
    status_text: str = ""

session.running = True と書くだけで、これを use_state で読んでいる全コンポーネントが自動再描画される。React の Recoil / Jotai 風だが、追加のライブラリ無しで Flet 標準で出来る

業務アプリで頻発する「裏で長いジョブが走っているときに、画面のステータスバーとプログレスバーとボタンの enable/disable が連動する」を、配線コードゼロで実現できるのが効く。


3. Python だけで vector chart を描く ― flet.canvas

業務アプリで頻出する「自前グラフ」 ― aiseed-weather では当初 matplotlib を ft.Image に貼り付けていたが、ホバーやクリックを後付けしたい都合で、flet.canvas で描き直した。これが 650 行で 5 変数 × HRES + MSM + 気候値 + アンサンブル帯の重ね描きができる:

import flet.canvas as cv

shapes: list[cv.Shape] = []

# 気候値の範囲帯 (p25 〜 p75)
shapes.append(cv.Path(
    elements=band_path_elements,
    paint=ft.Paint(color="#d8e4f5", style=ft.PaintingStyle.FILL),
))

# HRES 折れ線
shapes.append(cv.Path(
    elements=hres_line_elements,
    paint=ft.Paint(color="#234b86", stroke_width=2.0,
                   style=ft.PaintingStyle.STROKE),
))

# 軸ラベル
for tick in time_ticks:
    shapes.append(cv.Line(x, pad_t, x, pad_t + plot_h,
                          paint=ft.Paint(color="#cccccc")))
    shapes.append(cv.Text(x, pad_t + plot_h + 14, label,
                          style=ft.TextStyle(size=10)))

return cv.Canvas(shapes=shapes, width=W, height=H)

Web フロントエンドだったら d3.js / Recharts / Plotly + データ整形コードが必要なところを、すべて Python で完結している。データソースの polars DataFrame から直接 Canvas の Path 要素を組み立てている。「データ処理と描画の言語が同じ」というのが、業務開発でじわじわ効いてくる。


4. 出力用は別経路 ― matplotlib も同じコードベースで

画面表示は flet.canvas だが、「PNG ダウンロード」ボタンを押したときの出力は matplotlib で生成している:

fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(times, temperatures, label="HRES")
ax.fill_between(times, p25, p75, alpha=0.3, label="気候値範囲")
fig.savefig(path, dpi=150, metadata={"Source": "ECMWF Open Data / ..."})

インタラクティブ用とエクスポート用で別エンジン、しかし両方 Python なので「変数追加」「色の統一」が一箇所で済む。Web アプリで「画面は React + 出力は別バックエンドで Pillow」みたいな分裂が起きないのが大きい。


5. ネイティブの非同期 I/O ― asyncio がそのまま動く

ECMWF の S3 からの GRIB2 ダウンロードはネットワーク律速で並列化したい。 asyncio.TaskGroup + httpx.AsyncClient を、Flet のイベントハンドラから直接呼ぶ:

async def on_fetch(_):
    session.running = True  # 全コンポーネントが自動更新
    try:
        async with asyncio.TaskGroup() as tg:
            for param in params:
                tg.create_task(download_one(param, session))
    except* asyncio.CancelledError:
        session.status_text = "キャンセル"
    finally:
        session.running = False

VB 6 時代の DoEvents 地獄も、Electron の IPC 配線地獄もない。 ネイティブ Python の async が、UI の更新サイクルと自然に噛み合っている


6. ネイティブ依存も問題なし ― miniforge / conda-forge

業務アプリは「Python から C ライブラリを呼ぶ」が頻発する。 aiseed-weather は:

これらは pip では設定が辛いが、miniforge を使えば environment.yml 一発で揃う:

dependencies:
  - python=3.13
  - flet>=0.85
  - polars
  - xarray
  - cfgrib
  - cartopy
  - matplotlib
mamba env create --prefix ./.venv -f environment.yml
mamba activate ./.venv
flet run src/aiseed_weather/main.py

Flet と科学計算スタックが衝突しない。これは Electron 系では地獄に近い領域 (各 OS のネイティブビルド) で、Python + conda の組み合わせの強さがそのまま生きる。


7. 配布もコマンド一発

flet pack src/aiseed_weather/main.py  # 単一バイナリ
flet build apk                         # Android
flet build web                         # PWA

同じコードベースから、デスクトップアプリ、Web アプリ、モバイルアプリを生成できる。業務 SI の世界では「同じツールを社内 Web と現場タブレットで」が頻出するので、これは事実上の killer feature。


8. 組み込み・業務専用端末にも刺さる

Flet が業務 GUI で強いのはデスクトップ / Web だけではない。 Flutter ベースのレンダリングが GPU を素直に使うため、ARM ボード級のハードでもヌルヌル動く。これは業務向け専用端末を作る現場で重要な性質。

想定される用途

なぜ Flet が向くのか

要件 Flet の対応
Raspberry Pi 4/5 等で動く Flutter Linux desktop が動けば OK。実績多数
タッチ前提 UI Flutter / Material はタッチファースト設計
キオスク化 (フルスクリーン、Window chrome なし) page.window.full_screen = True 1 行
ハードと話す (GPIO / シリアル / CAN / Modbus / OPC-UA) Python 業務ライブラリがそのまま使える (pyserial, pymodbus, python-can, asyncua …)
画像処理・推論を同居 OpenCV, PyTorch, ONNX Runtime, TFLite すべて Python から
現場ごとのカスタマイズ コード 1 行差し替えて再配置、ビルドチェーン不要
監視を Web からも見たい 同じコードで flet run --web ― 端末側と遠隔監視ダッシュボードを 1 ソースで

aiseed-weather の例から類推できること

このアプリは「PC で動く解析ツール」として書かれているが、全く同じコードを Raspberry Pi 5 + 7" タッチディスプレイで動かしてキオスクモードにすれば、それだけで気象表示端末になる

def main(page: ft.Page):
    page.window.full_screen = True
    page.window.frameless = True
    page.theme_mode = ft.ThemeMode.DARK  # 夜間視認性
    page.padding = 0
    page.add(App())

ft.run(main)

3 行追加するだけで「業務専用端末」のガワが整う。matplotlib のグラフは Pi 5 でも 60 fps スムーズに描かれる。これが Flet の地味だが強烈な利点。

特筆すべきは「端末用ファームと、それを遠隔監視する Web ダッシュボードが同一ソース」になる点。業務 IoT で監視 Web をわざわざ別チームで作ってきた現場には、開発体制ごと変える破壊力がある。

弱点

「Raspberry Pi 級以上 + タッチパネル + 業務ロジック」のゾーンは、 Flet の独擅場になるかも。


帳票 / 印刷も実は強い ― Excel テンプレート方式

業務アプリで避けて通れない「帳票出力」 ― これは Flet の弱点ではなく、むしろ Python の伝統的な得意領域。日本の現場で de facto となっている Excel 帳票を、テンプレートに値を流し込む方式で生成できる:

from openpyxl import load_workbook

wb = load_workbook("template/月次報告.xlsx")
ws = wb["集計"]
ws["B3"] = report_date
ws["D5"] = total_count
for i, row in enumerate(df.iter_rows(named=True), start=10):
    ws.cell(i, 1, row["地点"])
    ws.cell(i, 2, row["最高気温"])
    ws.cell(i, 3, row["最低気温"])
wb.save(output_path)

書式・罫線・印刷範囲・ヘッダ/フッタ・社判の画像はテンプレート側で作り込んでおけば、Python は値を埋めるだけ。現場の経理 / 事務担当が Excel で直接テンプレートを編集できるので、運用に乗せやすい。

選べる出力経路:

出力 ライブラリ 用途
Excel (.xlsx) テンプレート差し込み openpyxl 月次報告、明細、見積書
Excel + チャート / 条件付き書式 xlsxwriter グラフ付き集計表
Word (.docx) テンプレート python-docx, docxtpl 契約書、報告書
PDF (低レベル) reportlab レイアウト固定の証憑
HTML → PDF weasyprint CSS で組んだレポート
プリンタ直送 win32print (Win), cups (Linux) ラベル発行、レシート

Flet コンポーネント側はボタン 1 つで済む:

ft.FilledButton(
    "帳票出力",
    icon=ft.Icons.PRINT,
    on_click=lambda _: export_excel(data, settings.template_path),
)

aiseed-weather の「PNG ダウンロード」と同じパターン ― 画面表示と出力ファイルで別エンジンを使い、両方とも Python なので 1 つのコードベースに収まる。


AI コーディングとの相性

ここまで紹介した特徴は、AI による自動コーディングと組み合わせたときに本領を発揮するaiseed-weather の UI コード 6,800 行は、ほぼすべて Claude Code との対話で書かれた。人間側がやったのは「何を作るか」「データの意味は何か」を伝えることと、出てきたコードのレビューだけ。

Flet が AI フレンドリーな理由は構造的:

  1. Python = LLM が最も得意な言語
  2. API 面が小さく、命名が一貫している (ft.Column / ft.Row / ft.Container / ft.Text …)
  3. 宣言的なので「状態 → UI」の一方向データフロー ― LLM が混乱しやすい「いつ何を更新するか」の手続き的判断が要らない
  4. HTML/CSS/JS が出てこない ― CSS セレクタ衝突、Tailwind クラス名、React のキー指定、といったクロスドメインの整合性チェックが不要

結果として、「データクラスを 1 つ書いて、それを表示するコンポーネントを作って」と頼むだけで、ほぼ修正なしで動く Flet コードが返ってくる。Web フロントだと "TypeScript の型を書き直して" "Tailwind が効かない" "useMemo が必要" など複数の往復が要るところが、Flet なら 1 ターンで決まる。

さらに、プロジェクト固有のコーディング規約は AI に常時参照させる仕組み (Claude Code の Skill 機能など) でリポジトリに同梱できる。「うちは Flet 0.85+ 宣言的モードのみ、page.update() は禁止」と一度書いておけば、Web 検索で混入する古い書き方を AI が選ぶことは無い。Flet の宣言的モードが「規約として書き下ろしやすい」ことと、 LLM のコード生成性能が良いこととが、ここで相乗効果を出す

(この「規約を AI に読ませる」運用そのものは、業務 SI の体制論として独立した話題なので、別稿で扱う。Python 側のホットスポットを Rust (PyO3) で潰す手法も同様、別稿予定。)


Visual Basic との対比 ― 何が継承され、何が現代化されたか

VB 6 Flet + Python
学習コスト 低い 同じく低い (Python が読めれば 1 日で動かせる)
GUI 配置 フォームデザイナ コード (Git, レビュー, AI 生成と相性◎)
イベント駆動 Button1_Click on_click=fn (async OK)
状態管理 グローバル変数 + フォームスコープ use_state + @ft.observable
数値計算 自前 / Excel 連携 numpy / polars / xarray が標準
可視化 MSChart, ActiveX matplotlib / flet.canvas / Plotly
ネットワーク WinINet, Winsock httpx, asyncio, gRPC, etc.
配布 EXE EXE / Web / iOS / Android / 組み込み端末
動作環境 Windows only Win / Mac / Linux / Web / Mobile / Raspberry Pi
エコシステム 業務 ActiveX Python パッケージ全部

VB 6 で生産性を支えていたのは「言語の単純さ × フォームの即時性 × 配布の単純さ」だった。Flet はこの三拍子を、Python という強力な裏方を引き連れて現代に再演している


こんな現場に向く

aiseed-weather を書きながら強く感じた、Flet の本領が出る場面:

  1. データを扱う社内ツール ― 解析・集計の中心が pandas / polars で書かれていて、それを業務担当者に GUI で配りたい
  2. ML 推論を業務に乗せたい ― モデル推論 (PyTorch / scikit-learn) を呼ぶ画面を、データサイエンス担当者自身が書ける
  3. 科学計算 + 可視化 ― matplotlib / Plotly の表現力をそのまま画面に埋め込める
  4. VB / VBA / Access からの脱却 ― 言語の生産性を落とさずクロスプラットフォーム化したい
  5. Electron / Tauri で挫折した ― JS スタックの面倒さを払拭したい
  6. Streamlit より本格的なものを書きたい ― 状態の自由度、UI コンポーネントの選択肢、配布形態の幅が決定的に違う
  7. 業務専用端末・組み込み HMI ― Raspberry Pi / Linux ボード + タッチパネルで動く、現場据え置きの専用 UI を、Python で書きたい

成熟ステータス ― 宣言的モードは production-ready 宣言済み

執筆時点で、公式 (Flet Team) は 宣言的モード = 実アプリで使える段階 だと明言している:

「新しい API だから様子見」というフェーズはもう終わっている。今から新規業務アプリを書き始めるなら、宣言的モード一択。

注意点としては「Web 上の古い記事は命令的 API (page.add() / 直接ミュテート) で書かれているものが多い」 ― ただし AI コーディング前提なら、規約ファイルに「宣言的モードのみ」と書いておけば AI 側は最新 API で生成するので、実害は無い。


弱点も正直に

それでも、業務 GUI を Python で書くという軸がもたらす利得が、これらを上回る場面は広い


まとめ ― 「Python の上の Visual Basic」が今ここにある

aiseed-weather は、Flet が「中規模の本格業務アプリ」を支えられるかどうかの個人的な検証だった。結論は、はっきり YES

これらを、Python 単一言語で、Web のフロント / バックエンド分離なしで、React も TypeScript もバンドラも CSS フレームワークも使わず に、ふつうに開発できている。Raspberry Pi にも載る。Web にも出せる。

VB 6 が「業務 GUI のミニマム生産性スタック」を発明したとすれば、 Flet はそれを Python の文脈で再発明した上で、配布先の天井を取り払った。 VB の生産性を懐かしむすべての開発者、Streamlit で物足りなさを感じているすべてのデータサイエンティスト、Electron で挫折したすべての Python 使い、現場端末を Qt で苦労して作ってきたすべての SI エンジニアに、flet run してみてほしい。

公式: https://flet.dev/ / ソース: aiseed-weather