Flatを少しでも理解するためにチュートリアルをやる - チャット
Flatとは
Fletは、Pythonを使ってWebアプリ、デスクトップアプリ、モバイルアプリを簡単に開発できるフレームワークです。
作るもの
チャットの作成
チュートリアルを参考にチャットアプリをつくって行きます。
実行環境
- flet Ver.0.21.2
- Python Ver.3.10.11
ライブラリのインストール
pip
を使いflet
をインストールします。
pip install flet
メッセージの表示
ユーザのメッセージ送信とメッセージ履歴表示の基礎を作ります。
このプログラムには以下のコントロールを使っています
-
Text
チャットを表示するテキスト -
TextField
チャットメッセージの記入 -
ElevatedButton
メッセージの送信ボタン -
Column
メッセージ(テキスト)を垂直に表示をする -
Row
TextFieldとElevatedButtonを水平に表示する
全体のコード:01.py
import flet as ft
def main(page: ft.Page):
# チャットメッセージを表示するコントロール
chat = ft.Column()
# 新規メッセージ入力欄
new_message = ft.TextField()
# 送信ボタンクリック時の処理
def send_click(e):
# 新しいメッセージを`chat`コントロールに追加
chat.controls.append(ft.Text(new_message.value))
# TextField(メッセージ)を空にする
new_message.value = ""
# 画面を更新
page.update()
page.add(
chat, ft.Row(controls=[new_message, ft.ElevatedButton("Send", on_click=send_click)])
)
ft.app(main, view=ft.AppView.WEB_BROWSER)
ユーザがメッセージを送信すると、send_click
を呼び出すイベントが発生します。
入力されたメッセージがchat
に追加されます。
そしてメッセージ入力欄は空になります。
view=ft.AppView.WEB_BROWSER
:Webから使えるようになります。
実行画面
チャットのやり取り
前のチャットアプリでは、複数のブラウザを開くと各タブで独立したチャットの履歴が保持されていました。
FeltのPubSubを使うことで、リアルタイムに別々のブラウザ間でメッセージのやり取りをすることが可能になります。
- メッセージ受信処理の登録
- ユーザがメッセージの受信するための処理を登録します。
-
on_message
関数はメッセージが送られてきたときに呼ばれます。
page.pubsub.subscribe(on_message)
- メッセージ受信処理
- 送られてきたメッセージを、チェット履歴に登録します。
def on_message(message: Message):
# 送られてきたメッセージをchatに追加
chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
page.update()
- メッセージ送信処理
- ユーザが送信ボタンを押したときに呼ばれます。
- 送信されたメッセージを
Message
オブジェクトに変換し、page.pubsub.send_all()
を使いすべてのユーザに配信します
全体のコード:02.py
import flet as ft
class Message():
def __init__(self, user: str, text: str):
self.user = user
self.text = text
def main(page: ft.Page):
page.title = 'チャット'
chat = ft.Column()
new_message = ft.TextField()
def on_message(message: Message):
# 送られてきたメッセージをchatに追加
chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
page.update()
# 全クライアントに対してメッセージを配信するためのPub/Subチャンネルを登録
page.pubsub.subscribe(on_message)
def send_click(e):
# メッセージを送信 (Pub/Subを利用)
page.pubsub.send_all(Message(user=page.session_id, text=new_message.value))
new_message.value = ''
page.update()
page.add(
chat, ft.Row(controls=[new_message, ft.ElevatedButton("Send", on_click=send_click)])
)
ft.app(target=main, view=ft.AppView.WEB_BROWSER)
実行画面
- チャットのやり方
- ブラウザを2つ用意します。
- チャット画面があるブラウザからURLをコピー
- 何もないブラウザにURLをペースト
注意
タブを複製すると複製元のブラウザのセッションIDが同じになります。
セッションIDが同じなので片方のブラウザが同期させません。
ユーザ名設定
前に作成したチャットアプリには基本機能ができています。
ですが、ユーザ名がセッションIDで誰とメッセージのやり取りをしているのかがあまり分からないため、使いやすいとは言えません。
セッションIDの代わりにユーザ名を表示するように改良していきます。
ユーザ名を取得するために、AlertDialog
を使います。
user_name = ft.TextField(label="Enter your name")
page.dialog = ft.AlertDialog(
open=True, # Trueだとプログラム開始時に開く
modal=True,
title=ft.Text("Welcome!"),
content=ft.Column([user_name], tight=True),
actions=[ft.ElevatedButton(text="Join chat", on_click=join_click)],
actions_alignment="end"
)
チャットに参加するとすべてのユーザにチャットに参加したことが通知されるメッセージが送信されます。
参加メッセージがチャットメッセージを区別するためにMessage
クラスにmessage_type
プロパティを追加
class Message():
def __init__(self, user: str, text: str, message_type: str):
self.user = user
self.text = text
self.message_type = message_type
message_type
の区別するためにon_message
を以下のコード変更にします。
def on_message(message: Message):
if message.message_type == "chat_message":
chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
elif message.message_type =="login_message":
chat.controls.append(
ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)
)
page.update()
チャットに参加したときとメッセージを送信したとの2つのイベントができました。
AlertDialog
に入力されたユーザ名前をsession
を使いを保存できるようにします。
def join_click(e):
if not user_name.value:
user_name.error_text = "Name cannot be blank!"
user_name.update()
else:
page.session.set("user_name", user_name.value)
page.dialog.open = False
page.pubsub.send_all(Message(user=user_name.value, text=f"{user_name.value} has joined the chat.", message_type="login_message"))
page.update()
また、ユーザ名が入力されていなければエラーが出るようになっています。
全体のコード:03.py
import flet as ft
class Message():
def __init__(self, user: str, text: str, message_type: str):
self.user = user
self.text = text
self.message_type = message_type
def main(page: ft.Page):
page.title = 'チャット'
chat = ft.Column()
new_message = ft.TextField()
def on_message(message: Message):
if message.message_type == "chat_message":
chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
elif message.message_type =="login_message":
chat.controls.append(
ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)
)
page.update()
page.pubsub.subscribe(on_message)
def send_click(e):
page.pubsub.send_all(Message(user=page.session.get('user_name'), text=new_message.value, message_type="chat_message"))
new_message.value = ''
page.update()
user_name = ft.TextField(label="Enter your name")
def join_click(e):
if not user_name.value:
user_name.error_text = "Name cannot be blank!"
user_name.update()
else:
page.session.set("user_name", user_name.value)
page.dialog.open = False
page.pubsub.send_all(Message(user=user_name.value, text=f"{user_name.value} has joined the chat.", message_type="login_message"))
page.update()
page.dialog = ft.AlertDialog(
open=True, # Trueだとプログラム開始時に開く
modal=True,
title=ft.Text("Welcome!"),
content=ft.Column([user_name], tight=True),
actions=[ft.ElevatedButton(text="Join chat", on_click=join_click)],
actions_alignment="end"
)
page.add(
chat, ft.Row(controls=[new_message, ft.ElevatedButton("Send", on_click=send_click)])
)
ft.app(target=main, view=ft.AppView.WEB_BROWSER)
ユーザーインターフェース
チャットアプリを豪華に見せる機能追加します。
全体のコード:main.py
表示例
メッセージには、イニシャル入りのアイコンと、ユーザ名とメッセージを表示します。
アイコンにはCircleAvatar
を使います。
チャットアプリは多くのメッセージを表示する必要があるため、再利用可能なカスタムコントロールを作成することが合理的です。
Row
を継承したChatMessage
クラスを新しく作ります。
-
ChatMessage
クラスはインスタンス作成時に、Message
オブジェクトからユーザ名とメッセージを取り出しそれらを表示する。 -
get_initials
メソッドは、ユーザ名の頭文字の取得 -
get_avatar_color
メソッドは、ユーザ名に基づきhash関数を使用して、事前に決めた色のリストからランダムに決める
class ChatMessage(ft.Row):
def __init__(self, message:Message):
super().__init__()
self.vertical_alignment = "start"
self.controls = [
ft.CircleAvatar(
content=ft.Text(self.get_initials(message.user_name)),
color=ft.colors.WHITE,
bgcolor=self.get_avatar_color(message.user_name)
),
ft.Column(
[
ft.Text(message.user_name, weight="bold"),
ft.Text(message.text, selectable=True)
],
tight=True,
spacing=5,
)
]
# ユーザ名の頭文字の取得
def get_initials(self, uset_name: str):
return uset_name[:1].capitalize()
# ユーザ名に基づき、hash関数を使用して、事前に決めた色のリストからランダムに決める
def get_avatar_color(self, uset_name: str):
colors_lookup = [
ft.colors.AMBER,
ft.colors.BLUE,
ft.colors.BROWN,
ft.colors.CYAN,
ft.colors.GREEN,
ft.colors.INDIGO,
ft.colors.LIME,
ft.colors.ORANGE,
ft.colors.PINK,
ft.colors.PURPLE,
ft.colors.RED,
ft.colors.TEAL,
ft.colors.YELLOW,
]
return colors_lookup[hash(uset_name) % len(colors_lookup)]
いくつかの関数や変数を変更していきます。
Message
クラスのuser
インスタンスをuser_name
に変更します。
class Message():
- def __init__(self, user: str, text: str, message_type: str):
+ def __init__(self, user_name: str, text: str, message_type: str):
- self.user = user
+ self.user_name = user_name
self.text = text
self.message_type = message_type
-
send_click
関数名をsend_message_click
名に変更します。 -
Message
クラスを使っている引数名user
をuser_name
に変更します。
- def send_click(e):
+ def send_message_click(e):
if new_message.value != "":
- page.pubsub.send_all(Message(user=page.session.get('user_name'), text=new_message.value, message_type="chat_message"))
+ page.pubsub.send_all(Message(user_name=page.session.get('user_name'), text=new_message.value, message_type="chat_message"))
new_message.value = ''
new_message.focus()
page.update()
-
Message
クラスを使っている引数名user
をuser_name
に変更します。 -
user_name
をjoin_user_name
に変更します。
- user_name = ft.TextField(label="Enter your name")
+ join_user_name = ft.TextField(label="Enter your name")
def join_click(e):
- if not user_name.value:
- user_name.error_text = "Name cannot be blank!"
- user_name.update()
+ if not join_user_name.value:
+ join_user_name.error_text = "Name cannot be blank!"
+ join_user_name.update()
else:
- page.session.set("user_name", user_name.value)
+ page.session.set("user_name", join_user_name.value)
page.dialog.open = False
- page.pubsub.send_all(Message(user=user_name.value, text=f"{user_name.value} has joined the chat.", message_type="login_message"))
+ page.pubsub.send_all(Message(user_name=join_user_name.value, text=f"{join_user_name.value} has joined the chat.", message_type="login_message"))
page.update()
page.dialog = ft.AlertDialog(
open=True,
modal=True,
title=ft.Text("Welcome!"),
- content=ft.Column([user_name], tight=True),
+ content = ft.Column([join_user_name], tight=True),
actions=[ft.ElevatedButton(text="Join chat", on_click=join_click)],
actions_alignment="end"
)
レイアウトをアップデートしていきます。
ChatMessage
クラスがユーザ名やメッセージを作成するので、on_message
関数を書き換えていきます。
def on_message(message: Message):
if message.message_type == "chat_message":
- chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
+ m = ChatMessage(message)
elif message.message_type =="login_message":
- chat.controls.append(
- ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)
- )
+ m = ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)
chat.controls.append(m)
page.update()
新しいレイアウトにするためにいくつかの改善点があります。
- メッセージを表示する
Column
からListView
に変更します。ListView
にすることでスクロールできるようになります。 -
ListView
の周りにボーダーを表示するContainer
の追加 -
ElevatedButton
からIconButton
に変更 - スペースを埋めるために
Container
にexpand
を追加
変更追記したコード
新しいチャットメッセージ
- chat = ft.Column()
+ chat = ft.ListView(
+ expand = True,
+ spacing = 10,
+ auto_scroll = True
+ )
- ListView
-
spacing
:アイテム間の高さを指定できます。 -
auto_scroll
:True
にした場合、スクロールバーの位置を自動的に端に移動させます。
-
新しい入力フォーム
- new_message = ft.TextField()
+ new_message = ft.TextField(
+ hint_text = "Write a message...",
+ autocorrect = True,
+ shift_enter = True,
+ min_lines = 1,
+ max_lines = 5,
+ filled = True,
+ expand = True,
+ on_submit = send_message_click
+ )
- TextField
-
shift_enter
:shift+enter
で改行することができます。 -
filled
:塗りつぶしをします。 -
on_submit
:TextField
にフォーカスがあっているときにENTERキーを押すとイベントが発生します。
-
新しいページ
- page.add(
- chat, ft.Row(controls=[new_message, ft.ElevatedButton("Send", on_click=send_click)])
- )
+ page.add(
+ ft.Container(
+ content = chat,
+ border = ft.border.all(1, ft.colors.OUTLINE),
+ border_radius = 5,
+ padding = 10,
+ expand = True,
+ ),
+ ft.Row(
+ [
+ new_message,
+ ft.IconButton(
+ icon = ft.icons.SEND_ROUNDED,
+ tooltip = "Send message",
+ on_click = send_message_click
+ )
+ ]
+ )
+ )
- IconButton
-
tooltip
:カーソルが置かれたときに表示されるテキスト
-
最後
以上になります。
GitHubにすべてのコードを上げています。
電卓のチュートリアルも記事にしています。
Discussion