はじめに
PythonでFlutterアプリが作れるフレームワーク「Flet」の検証をしました。
FletではControl(画面要素)をコンポーネント化して再利用しやすくできるようだったので、その方法を試してみることにしました。
Fletの公式サイトはこちら。
https://flet.dev/
参考)過去記事はこちら。
前提事項
- 「Flet」に関する詳細は省略
- インストール方法、実行方法、デプロイ方法なども省略
- 試した環境
- Windows10
- Python 3.10.8
- Flet 0.3.2
概要
- Fletでログインフォームを作り
- それをコンポーネント化してみる
- 併せてイベントの実装方法も確認する
最終形を「Fly.io」にデプロイしたものが以下です。
https://flet-report02.fly.dev/#/
個人的な感想は以下。
- きちんと作るならコンポーネント化は必須になると思う
- 簡単なツールだったら、main に全部書いていっても良いと思う
- 今後の課題
- 作った class にプロパティやイベントを持たせる形にできないのか
- 日本語が最初☒の表示になるのは何とかならないのか
詳細
基本のソース構成
import flet as ft
class Component(ft.UserControl):
"""sample"""
def __init__(self):
super().__init__()
def build(self):
return ft.Card(
ft.Container(
ft.Column(
[
ft.Text("sample"),
ft.TextField(),
ft.ElevatedButton("button"),
],
alignment=ft.MainAxisAlignment.CENTER,
),
width=400,
padding=30,
),
)
def main(page: ft.Page):
component1 = Component()
component2 = Component()
component3 = Component()
# Page レイアウト
page.title = "sample"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.add(component1)
page.add(component2)
page.add(component3)
if __name__ == "__main__":
ft.app(target=main)
コンポーネント化する場合の基本のソース構成は↑。
- UserControl を継承してクラスを作る
- メソッド build(self) でコンポーネントの中身を作って return する
- コンポーネントを使う方は、作ったクラスを Control の1つとして生成してレイアウトに組み込む
サンプルでは、Text、TextField、ElevatedButtonをまとめてコンポーネント化しています。
mainでコンポーネントを3つ生成してそのまま配置。
↓がこれを実行した結果です。
同じものが3つ配置され、コンポーネントを変えれば3か所とも変わるような作りになります。
ログインフォームをコンポーネント化(レイアウトのみ)
ログインフォームをコンポーネント化してみたものが↓のサンプルコード。
ID、Passwordを入力してログインボタンを押すという想定でControlを配置。
実行結果が以下です。
Password入力用のプロパティがあるので、簡単に伏字にできます。
サンプルコードはこちら
import flet as ft
class FormLogin(ft.UserControl):
"""Login フォーム"""
def __init__(self):
super().__init__()
def build(self):
# I/O Controls
self.tfLoginid = ft.TextField(label="Login ID")
self.tfPassword = ft.TextField(
label="Password", password=True, can_reveal_password=True)
self.btnLogin = ft.ElevatedButton("ログイン")
# Content
header = ft.Container(
ft.Text("ログイン"),
)
body = ft.Column(
[
self.tfLoginid,
self.tfPassword,
],
)
footer = ft.Row(
[
self.btnLogin,
],
)
return ft.Card(
ft.Container(
ft.Column(
[
header,
body,
footer,
],
alignment=ft.MainAxisAlignment.CENTER,
),
width=400,
padding=30,
),
)
def main(page: ft.Page):
# I/O Controls
fromLogin = FormLogin()
# Page レイアウト
page.title = "Login form"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.add(fromLogin)
if __name__ == "__main__":
ft.app(target=main)
デザイン調整とイベント処理追加
さらにデザイン調整とイベント処理を追加したものが↓のサンプルコード。
「ログイン」ボタンを押したときに main にある login_auth() が呼ばれるようにしてあります。
簡易的に「hoge/hoge」で「ログイン成功」と snack_bar が出るようにしてあります。
サンプルコードはこちら
import flet as ft
class FormLogin(ft.UserControl):
"""Login フォーム"""
# const
CONTENT_WIDTH = 400
CONTENT_HIGHT = 400
def __init__(self, func_login_auth=None):
self.func_login_auth = func_login_auth
super().__init__()
def build(self):
# I/O Controls
self.tfLoginid = ft.TextField(label="Login ID", autofocus=True)
self.tfPassword = ft.TextField(
label="Password", password=True, can_reveal_password=True)
self.btnLogin = ft.ElevatedButton(
"ログイン", on_click=self.login_clicked)
# Content
header = ft.Container(
ft.Text("ログイン", style=ft.TextThemeStyle.DISPLAY_MEDIUM, weight=ft.FontWeight.BOLD,
width=self.CONTENT_WIDTH, text_align=ft.TextAlign.CENTER),
margin=ft.margin.only(bottom=20),
)
body = ft.Column(
[
self.tfLoginid,
self.tfPassword,
],
)
footer = ft.Row(
[
self.btnLogin,
],
alignment=ft.MainAxisAlignment.CENTER,
)
return ft.Card(
ft.Container(
ft.Column(
[
header,
body,
ft.Divider(),
footer,
],
alignment=ft.MainAxisAlignment.CENTER,
),
width=self.CONTENT_WIDTH,
height=self.CONTENT_HIGHT,
padding=30,
),
)
def login_clicked(self, e):
if self.func_login_auth:
loginid = self.tfLoginid.value
password = self.tfPassword.value
self.func_login_auth(loginid, password)
def main(page: ft.Page):
def login_auth(loginid, password):
if loginid == "hoge" and password == "hoge":
txtMesssage.value = f"ログイン成功! {loginid} {password}"
txtMesssage.color = ft.colors.GREEN
else:
txtMesssage.value = f"ログイン失敗! {loginid} {password}"
txtMesssage.color = ft.colors.RED
page.snack_bar.open = True
page.update()
# I/O Controls
fromLogin = FormLogin(func_login_auth=login_auth)
txtMesssage = ft.Text(size=30)
# Page レイアウト
page.title = "Login form"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.add(fromLogin)
page.snack_bar = ft.SnackBar(txtMesssage)
if __name__ == "__main__":
ft.app(target=main)
ソース最終形
せっかくコンポーネントにしたので、モーダルダイアログでログインフォームを出すボタンを下に追加。
モーダルダイアログも簡単に出せることを確認できました。
実行結果は概要に貼ってあるものです。
サンプルコードはこちら
import flet as ft
class FormLogin(ft.UserControl):
"""Login フォーム"""
# const
CONTENT_WIDTH = 400
CONTENT_HIGHT = 400
def __init__(self, func_login_auth=None):
self.func_login_auth = func_login_auth
super().__init__()
def build(self):
# I/O Controls
self.tfLoginid = ft.TextField(label="Login ID", autofocus=True)
self.tfPassword = ft.TextField(
label="Password", password=True, can_reveal_password=True)
self.btnLogin = ft.ElevatedButton(
"ログイン", on_click=self.login_clicked)
# Content
header = ft.Container(
ft.Text("ログイン", style=ft.TextThemeStyle.DISPLAY_MEDIUM, weight=ft.FontWeight.BOLD,
width=self.CONTENT_WIDTH, text_align=ft.TextAlign.CENTER),
margin=ft.margin.only(bottom=20),
)
body = ft.Column(
[
self.tfLoginid,
self.tfPassword,
],
)
footer = ft.Row(
[
self.btnLogin,
],
alignment=ft.MainAxisAlignment.CENTER,
)
return ft.Card(
ft.Container(
ft.Column(
[
header,
body,
ft.Divider(),
footer,
],
alignment=ft.MainAxisAlignment.CENTER,
),
width=self.CONTENT_WIDTH,
height=self.CONTENT_HIGHT,
padding=30,
),
)
def login_clicked(self, e):
if self.func_login_auth:
loginid = self.tfLoginid.value
password = self.tfPassword.value
self.func_login_auth(loginid, password)
def main(page: ft.Page):
def open_login(e):
dlgLogin.open = True
page.update()
def close_login(e):
dlgLogin.open = False
page.update()
def login_auth(loginid, password):
if loginid == "hoge" and password == "hoge":
txtMesssage.value = f"ログイン成功! {loginid} {password}"
txtMesssage.color = ft.colors.GREEN
else:
txtMesssage.value = f"ログイン失敗! {loginid} {password}"
txtMesssage.color = ft.colors.RED
page.snack_bar.open = True
page.update()
# I/O Controls
fromLogin = FormLogin(func_login_auth=login_auth)
txtMesssage = ft.Text(size=30)
# Add Dialog
btnLoginDialog = ft.ElevatedButton("ログイン Dialog", on_click=open_login)
dlgLogin = ft.AlertDialog(
modal=True,
title=ft.Text("Login"),
content=FormLogin(func_login_auth=login_auth),
actions=[ft.TextButton("Cancel", on_click=close_login)],
actions_alignment=ft.MainAxisAlignment.END,
)
# Page レイアウト
page.title = "Login form"
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.add(fromLogin)
page.add(ft.Divider())
page.snack_bar = ft.SnackBar(txtMesssage)
# Add Dialog
page.add(btnLoginDialog)
page.dialog = dlgLogin
if __name__ == "__main__":
ft.app(target=main)
以上です。