👀

Flet1.0での大幅進化に向けて簡単アプリを作成して予習してみる

に公開

導入

私は業務で最も使用する言語がPythonのため、簡単な社内用ツールなどの作成にFletを用いることが多かったのですが、
先日 Flet公式ブログ が2025年内でのバージョン1.0リリースに向けてAlpha版を公開していたので、来る破壊的変更に向けて簡単なアプリ作成をしながら挙動の予習をしてみました。
https://flet.dev/blog

Fletとは

FletとはFlutterをラップしたPythonのみでマルチプラットフォームにGUIアプリケーションを開発できるフレームワークです。

Pythonの豊富なライブラリ資産を活かしながらモダンなUIのアプリケーションを作成でき、2025年8月現在最新バージョンが0.28.3と安定版でないながらも開発が活発で将来性のあるフレームワークです。
https://flet.dev/

Flet1.0で何が変わるのか

このセクションの内容は 公式ブログ にも書かれているので読み飛ばしていただいても構いません。
現在提供されている情報によると以下のような変更が入るようです。

  • 従来の命令型スタイルに加えて、Flet アプリを構築するための宣言型アプローチ。
  • 自動更新- イベント ハンドラーの完了後にページが自動的に更新されます。
  • サービス- UI の再構築やナビゲーションをまたいで存続する、永続的な非 UI コンポーネントで>す。、、などの既存のコントロールはAudioサービスFilePickerとしてClipboard書き直されました。
    Web アプリの完全な WASM (WebAssembly) サポート- 最新のブラウザーでのダウンロードとパフォーマンスが高速化されます。
  • Web アプリのオフライン (CDN なし) モード- Flutter リソースと Pyodide がアプリにバンドルされています。
  • Flet アプリを既存の Web ページに埋め込む- 任意の Web ページ上の HTML 要素に Flet アプリをレンダリングします。
  • 拡張拡張 API - スプラッシュ画面や読み込み画面などの将来のカスタマイズのための余地を残しつつ、コントロールとともにサービスをエクスポートします。

色々ありますが、従来の命令的UIからReactのような宣言的UIになる部分が大きな破壊的変更となっています。

従来のFletの書き方

従来のFletは命令的UIを採用しており、開発者はUIの状態変化を全て自身で書く必要があります。
具体的にはpage.update()メソッドを使用してページを再レンダリングする処理をあらゆる場所で記述します。

main.py
import flet as ft

def main(page: ft.Page):
    txt_number = ft.TextField(value="0", text_align=ft.TextAlign.RIGHT, width=100)

    def plus_click(e):
        txt_number.value = str(int(txt_number.value) + 1) # UIの値を更新
        page.update() # UIを再レンダリング

    page.floating_action_button = ft.FloatingActionButton(
        icon=ft.Icons.ADD, on_click=plus_click
    )

    page.add(
        ft.Container(
            txt_number,
            alignment=ft.Alignment.CENTER,
        )
    )

ft.app(main)

従来のやり方だと何が問題なのか

上記のサンプルコードレベルのコード量だと感じづらいですが、UI部品は別ファイルに分けたり、複数のグローバルステートを管理する必要があるSPAだと中々辛くなってきます。

ある操作が別の複数のUI部品に対して状態変化を起こすような関数などが増えると状態更新のためのコードも肥大化していきます。
私はpageインスタンスでグローバルステートを管理して、ローカルステートはFletコントロールを継承した自作UIクラスなどで管理をして整理していました。

新しいFletの書き方

先程の従来の書き方のサンプルコードをFlet1.0で書いたものが以下になります。

状態とそれに関連する状態更新関数をデータクラスで定義して、
UI部品はそれらをft.ControlBuilderコントロールの中で宣言するだけで、状態更新関数が実行されると自動的にUIが再レンダリングされます。

main.py
from dataclasses import dataclass

import flet as ft

# 状態とそれに関連する状態更新関数を定義したデータクラス
@dataclass
class AppState:
    count: int

    def increment(self):
        self.count += 1


def main(page: ft.Page):
    state = AppState(count=0)

    page.floating_action_button = ft.FloatingActionButton(
        icon=ft.Icons.ADD, on_click=state.increment
    )
    page.add(
        ft.ControlBuilder(
            state,
            lambda state: ft.SafeArea(
                ft.Container(
                    ft.Text(value=f"{state.count}", size=50),
                    alignment=ft.Alignment.center(),
                ),
                expand=True,
            ),
            expand=True,
        )
    )


ft.run(main)

いかがでしょうか?
パッと見コード量が増えたように見えますが、ステートが増えると管理のしやすさは圧倒的にこちらが有利です。

また、Flutterを触ったことがある人ならお気づきですが、従来よりも更にFlutterに寄せたコードの書き方になっています。
しかしFlutterと違ってコンストラクタ周りの記述が一切不要なのは凄い楽ちんですね。

アプリを作成して挙動を確認してみた

記事冒頭でも触れましたが、現在Fletバージョン1.0に向けてAlpha版が公開されています。
公式ブログだけ読んでいてもつまらないので、このAlpha版Flet1.0を使ってアプリを作成してみました。

作成したアプリ

題材選定ですが、社内でやや需要のあったPDFの圧縮・結合・抽出などが簡単なGUI操作でできるアプリを作ってみました。
Windows64bit限定です。
https://github.com/harumiWeb/flet1.0alpha-pdf-compressor-app

とはいってもビジネスロジックの部分は外部ソフトウェアのバイナリを埋め込んでいて私はフロントエンドの実装しかしていません。
宣言的UIを触れるのには丁度いい題材でした。

状態管理したいところ

UIは以下の画像のようなSPA形式にしました。

  • メイン画面

  • 出力するページを設定できる画面

ざっくり状態管理する項目を挙げると

  • サイドバーの設定項目
  • 選択されたファイルの情報
  • 処理後のファイルの情報

などかと思います。

宣言的UIで実装してみる

状態側の処理

今回はstate.pyというファイルにアプリ内で使うステートを宣言してみました。

Flet1.0のステートクラスはただのdataclassなので、ステートクラスの中にステートクラスをネストすることで複雑なデータ型も問題なく扱えます。

AppGlobalStateデータクラスで宣言している内容がアプリのグローバルステートにあたります。
https://github.com/harumiWeb/flet1.0alpha-pdf-compressor-app/blob/main/state.py

雑なところはありますが、こうやって書いてみると、状態と状態更新関数が一つのクラスでまとめられるので非常にコードの管理がしやすいです。

UI側の処理

UI側のコードは以下のように実装してみました。
エントリーポイントなのであまりUIの記述はないですが、AppView関数上部のほうで選択済みファイルタブと処理済みファイルタブを宣言して値と更新関数にグローバルステートを使用しているのが見て取れるかと思います。
https://github.com/harumiWeb/flet1.0alpha-pdf-compressor-app/blob/main/main.py#L20-L87

その他従来のFletと実装が大きく変わったところ

- on_clickなどで発火する関数が非同期になった

これ、かなり大きな変更なんですが、インタラクティブなUI部品に設定できるon_~系の処理が非同期関数として実行されるようになりました。

つまり、通常の関数ではなくasync defのように宣言する必要があります。
そして注意しないといけないのが、非同期関数内でpageインスタンス組み込みのUIに変化をもたらす関数は処理が後回しにされるということです。

このあたりはAlpha版のため、実際の1.0では仕様が変わる可能性がありますが、Alpha版段階では上記仕様だったため、少し工夫が必要でした。
https://github.com/harumiWeb/flet1.0alpha-pdf-compressor-app/blob/main/components/sidebar.py#L201-L219

Flet組み込みのダイアログを表示、非表示する関数としてpage.show_dialog()page.pop_dialog()を使っているのですが、これらは非同期関数ではないためawaitすることができず、
従来のやり方で実装すると、処理中のダイアログの表示を待たずに処理ロジックが走って順番を制御できませんでした。

なので、await asyncio.sleep()を使って擬似的にawaitを入れて順序を制御してみました。

この辺の挙動は従来のFletに慣れている人だとハマるかもしれないので、もう少し楽な実装ができるようになることを期待したいです。

細かい実装方法の違いなど

アプリのローカルストレージに値を格納しておくメソッドとして従来は以下のようなpage.client_storageを使っていましたが、

old.py
page.client_storage.set("key", "value")
value = page.client_storage.get("key")

こちらはpage.shared_preferencesに置き換わり、値の取得処理は非同期になりました。

new.py
theme = await page.shared_preferences.get_async("theme")

Flutterでストレージ管理モジュールとしてshared_preferencesがよく使われていて、Fletのストレージ管理も内部的にshared_preferencesを使っているので、こちらもFlutterにより近くなったということでしょう。

他にも多くの場所に破壊的変更が入っているのですが、現在進行系で再設計されているのでソースコードを解読してみると色々発見があって楽しいので見てみてください👀

Flet1.0Alphaを触った感想

ざっくり感想

  • 状態管理めっちゃ楽になった!
  • 非同期処理とレンダリングの連携むずい
  • プロバイダーが無いので油断するとステートのバケツリレーになる

慣れなさから来る不便さはありましたが、慣れれば従来のFletよりも圧倒的に少ないコードでインタラクティブなアプリケーションを開発できるので安定版のリリースがとても楽しみになりました。

現在は0.70.0Alpha版として公開されているだけなので、本記事で紹介している挙動からまた大きな変更が加わる可能性はありますが、大本命の宣言的UIの部分はかなり出来てきているので、何か簡単なアプリを作って遊んでみるとすごく楽しいと思います!

ちょっとしたGUIツールにFlet、いかがでしょうか?

Discussion