はじめに
Python だけでクロスプラットフォームなアプリを作ることが出来る、Flet というフレームワークについての記事です。
Pythonだけで次のようなWeb・デスクトップに両対応したアプリを作ることが出来ます。
Flet の概要
Flet は Flutter をベースにしています。主に以下のような特徴があります。
-
From idea to app in minutes
- 「素早くGUIアプリを作成出来る」ことが、Fletの主なセールスポイントのようです。
-
Simple Architecture
- JSフロントエンドやRestAPIを書くこと無く、PythonだけでSPAを作る事が出来ます。
-
Batteries included
- Batteries Included は、Pythonの設計思想のようです。電池が付属している、つまりそのままでもすぐに動かせることを指します。
-
Powered by Flutter
- Flet の UI は Flutter で構成されているので、綺麗なUIを提供でき、マルチプラットフォームに対応しています。
-
Speaks your language
- Flet は言語に依存しないことを目指しています。現在サポートされているのはPythonのみですが、今後 Go や TypeScript、 C# にも対応していく予定のようです。
-
Deliver to any device
- 複数のプラットフォーム(iOS、Android、Web、Windows、Mac、Linux)に対応しています。
なお、モバイルサポートについては現在開発中のようで、ドキュメントには
ロードマップとビジョンが掲載されています。
そのため、この記事ではデスクトップアプリへのビルド、Webアプリとしてのデプロイのみを扱います。
Flet のユースケース
ざっくりまとめると、「使い慣れている単一の言語で」「クロスプラットフォームな」アプリを開発することが出来るので、スピードを重視する時に特に威力を発揮すると思われます。
社内向けのツール開発などに適していると言えそうです。
https://flet.dev/docs/tutorials/trello-clone#why-flet の要約
多くの開発者は、次のような場面に遭遇することが多いでしょう。
- 社内の非開発者向けに、比較的小規模のアプリを開発する必要がある
- CUI アプリを開発したら当初の予想よりも多くの人に使ってもらえることが分かったため、GUI化したい
このような状況で求められるのは主にスピードなので、electronのように巨大なツールやflutterのような機能豊富なフレームワーク、.NET MAUI などのクロスプラットフォームなフレームワークは適さないことが多いです。
Flet は、上のようなユースケースにおいて、見栄えが良く、ある程度パフォーマンスも良好で、手早くGUI アプリを提供することが出来ます。
Flutter の補足
Flutter は、 Google 社が開発しているオープンソースのフレームワークです。
主な特徴には以下のようなものがあります。
- クロスプラットフォームに対応しており、ワンソースで複数のプラットフォーム(iOS、Android、Web、Windows、Mac、Linux)に対応したアプリを開発することが出来る
- Dart言語により開発する
- 高速な動作
- ホットリロードのサポート
Flet の基本
ほぼ 公式チュートリアル の日本語訳です。
絶賛開発中のライブラリなので、極力公式ドキュメントを参照するようにしてください。
インストール
pip install flet
Linux, WSL の場合は追加でパッケージのインストールや設定が必要です。
https://flet.dev/docs/guides/python/getting-started#linux を参照してください。
Fletアプリの基本形
import flet as ft
def main(page: ft.Page):
# add/update controls on Page
pass
ft.app(target=main)
-
main
関数は Flet アプリケーションのエンドポイントです。この関数にはflet.Page
インスタンスが渡されます。 -
flet.app
の呼び出しにより、アプリケーションとして実行されます。- デフォルトではデスクトップアプリとして起動されます。
-
flet.app(view=ft.WEB_BROWSER)
のように変更することで、ブラウザで起動することが出来ます。
内部的にはWebアプリ
全ての Flet アプリは内部的には Web アプリで、ネイティブ OS のウィンドウで開かれたとしても、ビルトインのWebサーバーがバックグラウンドで起動しています。
Flet の Webサーバーは Fletd と呼ばれ、デフォルトではランダムな TCPポートでリッスンしています。
flet.app(port=8550, target=main)
のようにポートを指定して起動、ブラウザで http://localhost:8550
を開くと、FletアプリのWebバージョンが表示されます。
Control
Flet の UI は Flutter のモデルを簡略化した Control により構成されます。
※ 以下「Control」は「コントロール」とします
コントロールは、以下のいずれかにより表示されます。
- ページに追加する
- 他のコントロールの中に追加する
ページは最上位のコントロールです。
コントロールの入れ子は、ページをルートとするツリーとして表現されます。
コントロールのインスタンスを生成
コントロールはクラスとして実装されています。
例えば、 ft.Text
のインスタンスを生成するには次のようにします。
t = ft.Text(value="Hello, world!", color="green")
ページにコントロールを追加
ページにコントロールを追加するには、page.controls
にコントロールのインスタンスを追加し、page.update
を呼び出します。
import flet as ft
def main(page: ft.Page):
# Text Control 生成
t = ft.Text(value="Hello, world!", color="green")
# ページのコントロールリストに Control を追加
page.controls.append(t)
# ページを更新
page.update()
ft.app(target=main)
上のコードにより、次のようなUIが生成されます。
page.add(control)
という page.controls.append(control)
, page.update()
をまとめた関数も用意されています。
プロパティの更新
コントロールのプロパティを更新した後、 page.update
を呼び出すことでそれがページに反映されます。
次のコードでは、0 ~ 9 まで、 1秒ごとにテキストが更新されるテキストが生成されます。
import time
import flet as ft
def main(page: ft.Page):
t = ft.Text()
page.add(t)
for i in range(10):
t.value = f"Step {i}"
page.update()
time.sleep(1)
ft.app(target=main)
コンテナ
Page
や Row
、 Column
のように、他のコントロールを入れ子に出来るコントロールもあります。
import flet as ft
def main(page):
row = ft.Row(controls=[ft.Text("A"), ft.Text("B"), ft.Text("C")])
column = ft.Column(controls=[row, ft.Text("D")])
page.add(column)
ft.app(target=main)
イベントハンドラ
ボタンなどのコントロールは、 ElevatedButton.on_click
のように、ユーザの入力で発火するイベントハンドラを持つことが出来ます。
def button_clicked(e):
page.add(ft.Text("Clicked!"))
page.add(ft.ElevatedButton(text="Click me", on_click=button_clicked))
visible
プロパティ
表示/非表示を切り替えるプロパティです。全てのコントロールにデフォルトで True
が設定されています。
visible
を False
にすると、表示されなくなることは勿論、キーボードやマウスにより選択できず、イベントも発生しなくなります。
disabled
プロパティ
TextField
, Dropdown
, ボタンなど、ユーザから入力を受け取るコントロールで主に使用されます。
disabled
は親コントロールに設定することができ、その値は再帰的に全ての子コントロールに伝搬されます。
参照
コントロールはオブジェクトなので、そのプロパティにアクセスするには、それらのオブジェクトへの参照(変数)を保持する必要があります。
次のような例を考えてみましょう。
import flet as ft
def main(page):
first_name = ft.TextField(label="First name", autofocus=True)
last_name = ft.TextField(label="Last name")
greetings = ft.Column()
def btn_click(e):
greetings.controls.append(ft.Text(f"Hello, {first_name.value} {last_name.value}!"))
first_name.value = ""
last_name.value = ""
page.update()
first_name.focus()
page.add(
first_name,
last_name,
ft.ElevatedButton("Say hello!", on_click=btn_click),
greetings,
)
ft.app(target=main)
main
関数の冒頭で、 on_click
で使用する3つのコントロールを作成しています。
この調子でコントロールやイベントハンドラをどんどん追加していくと、コントロールの定義を一箇所にまとめることが難しくなってしまいそうです。
その結果、 pages.add()
のパラメータを見ただけでは、どのようなページが出来るのか想像するのが難しくなってしまうかもしれません。
このような問題を解消するために、 Flet にはコントロールへの参照を定義し、イベントハンドラでその参照を使用、後でページを構築する際に実際のコントロールへの参照を設定することが出来る Ref
というユーティリティクラスが用意されています。(お察しの通り、Reactから着想を得ているようです。)
Ref
を定義するには、次のようにします。
first_name = ft.Ref[ft.TextField]()
参照をコントロールに割り当てるには、以下のようにします。
page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True)
)
Ref
を使って先程の例を書き換えると、次のようになります。
import flet as ft
def main(page):
first_name = ft.Ref[ft.TextField]()
last_name = ft.Ref[ft.TextField]()
greetings = ft.Ref[ft.Column]()
def btn_click(e):
greetings.current.controls.append(
ft.Text(f"Hello, {first_name.current.value} {last_name.current.value}!")
)
first_name.current.value = ""
last_name.current.value = ""
page.update()
first_name.current.focus()
page.add(
ft.TextField(ref=first_name, label="First name", autofocus=True),
ft.TextField(ref=last_name, label="Last name"),
ft.ElevatedButton("Say hello!", on_click=btn_click),
ft.Column(ref=greetings),
)
ft.app(target=main)
これで、 page.add
の呼び出しを見るだけでページの構造がわかるようになりました。
命令形 UI
ここまでサンプルを見てきて、フロントエンド開発経験のある方は「Flutterベースなのに宣言型じゃないの?」という疑問を持たれた方も居るのではないでしょうか。
Flutterを始めとして、現在のフロントエンド開発で使われるフレームワークでは、データが変更されると自動的にUIが再構築される宣言的なモデルが採用されることが多いです。
一方で、Flet は手動でプロパティを更新することで UI をコントロールする命令形のモデルを実装しています。
あえてこの「古い」アプローチを採用することで、フロントエンドの開発経験がないプログラマーの開発ハードルを下げる狙いがあるようです。
Flet implements imperative UI model where you "manually" build application UI with stateful controls and then mutate it by updating control properties. Flutter implements declarative model where UI is automatically re-built on application data changes. Managing application state in modern frontend applications is inherently complex task and Flet's "old-school" approach could be more attractive to programmers without frontend experience.
デスクトップアプリにビルド
公式ドキュメントでは、ビルド方法として Pyinstaller によるビルドが紹介されています。
ここではpyinstallerのGUIラッパーであるauto-py-to-exe を使ってビルドしてみます。
auto-py-to-exeでビルド
インストールします。
pip install auto-py-to-exe
コマンドを叩いて起動します。
auto-py-to-exe
起動すると下のようなウィンドウが表示されると思います。
必要事項を入力します。
- Script Location : 配布したいPythonファイルを指定します。 ( ここでは
main.py
) - Onefile : One file を選択すると、文字通り1つのファイルとして出力されます。
- Console Window : コンソールを表示するかどうか選べます。GUIアプリの場合は表示しない方が良いでしょう。
入力出来たら「CONVERT...」 をクリックします。
しばらくして、下のような表示が出ればOKです。
「OPEN OUTPUT FOLDER」 をクリックすると、出力先のフォルダに飛びます。
ビルド結果
無事にファイル単体で実行出来るようになりました。
手元のWindows環境でも試してみましたが、同じように動作しました。
Webアプリとしてデプロイ
Web アプリとしてデプロイすることも出来ます。
Web socket
Flet は UIのリアルタイム部分更新と、プログラムへのイベント送信にWebSocketを使用します。
そのため、ホスティングプロバイダを選択するときには WebSocket に対応しているか確認する必要があります。
デプロイ先として 公式ドキュメント で紹介されていたのは fly.io と Replit です。
この記事では fly.io について扱います。
fly.io
fly.io は以下のような特徴があります。
Dockerfile
が使えるので特定の言語やランタイムに依存しない- デプロイしたアプリは各地のEdge Serverで実行され、高速なレスポンスが期待できる
- 東京Regionがある
- 特別な設定なしで自動的にHTTPSなURLを提供してくれる
- マネージドなPostgreSQLがある(無料枠付き!)
- 同じ組織からデプロイしたアプリはWireGuardのVPNを共有し、
<app-name>.internal
アドレスでお互いアクセスできる
fly にログイン
Install flyctl · Fly Docs を参考に、flyctl
をインストールします。
brew install flyctl
次のコマンドで fly にログインして( Github認証が使えます )、支払情報を登録します。
flyctl auth login
デプロイ準備
ここでは以下のファイルを用意します。
requiments.txt
fly.toml
Dockerfile
flet>=0.1.33
flet-getting-started
のところは好きな名前に変更してください。
app = "flet-getting-started"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
FLET_SERVER_PORT = "8080"
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
FROM python:3-alpine
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "./main.py"]
デプロイ
次のコマンドで Fly アプリをビルド、デプロイ出来ます。
flyctl apps create --name flet-getting-started
flyctl deploy
確認してみます。
flyctl apps open
無事デプロイ出来ました。
削除
不要になったアプリは削除しておきましょう。
flyctl apps destroy flet-getting-started
まとめ
以上、Fletの紹介でした。
私は非開発者向けの簡単なツールを作ることがよくあるので、Pythonで少しコードを書けば、Web・デスクトップに対応したアプリを作ることができるという点はとても魅力的です。モバイル対応も楽しみです。
余談ですが、この記事を書く際に今話題の ChatGPT を使ってみました。
「箇条書きに要約して」「添削して」などの言葉の後にDeepLで翻訳したドキュメントを突っ込むことで、ざっくりした概要を把握することが出来ます。
原文を読まないと意味が分からなかったり、「要約して」の時に重要なのに捨てられてしまう箇所が多少あったりしますが、初めて扱うライブラリのドキュメントを読む時にはかなり重宝しそうです。
参考
- 公式ドキュメント : https://flet.dev/docs/
- コントロールのリファレンス : https://flet.dev/docs/controls
- Todoアプリ、Trelloクローンなどの簡単なアプリ作成 : https://flet.dev/docs/tutorials
- サンプルアプリのリポジトリ : https://github.com/flet-dev/examples/tree/main/python