はじめに
Pythonのフレームワーク「Flet」について、公式チュートリアルにあるチャットアプリの仕組みを調べてみた😊
この記事で解決したいこと
✅Fletのアプリの仕組みをイメージできるようにする!
- チャットアプリのディレクトリ構成
- チャットアプリの処理を1つずつ理解
今回のゴール
✅チャットアプリの仕組みを理解する!
デモページ:https://flet-chat.fly.dev/
公式のGitHub:https://github.com/flet-dev/examples/blob/main/python/tutorials/chat/chat.py
公式のチュートリアル:https://flet.dev/docs/tutorials/python-realtime-chat/
チャットアプリのコードを見てみる
ディレクトリ構成
今から見ていくコード
✅公式のGitHubで紹介されているチャットアプリのコードを解説する。
コード全文
chat.py(GitHubのコードにコメントを付けたもの)
import flet as ft
# メッセージクラス
class Message():
    def __init__(self, user_name: str, text: str, message_type: str):
        self.user_name = user_name              # ユーザー名
        self.text = text                        # メッセージ
        self.message_type = message_type        # メッセージタイプ
# Rowを継承したコントロール
# チャットに必要なコントロールをまとめる
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, user_name: str):
        return user_name[:1].capitalize()
    # アバターの背景色を取得
    def get_avatar_color(self, user_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(user_name) % len(colors_lookup)]
def main(page: ft.Page):
    page.horizontal_alignment = "stretch"
    page.title = "Flet Chat"
    # ユーザー名を登録(チャットに参加するボタンがクリックされた時のイベントハンドラ)
    def join_chat_click(e):
        # 参加ユーザー名が空の場合
        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", join_user_name.value)
            page.dialog.open = False
            # 新しいメッセージのプレフィックスを設定(ユーザー名の後に「:」を付ける」)
            new_message.prefix = ft.Text(f"{join_user_name.value}: ")
            # ログインメッセージを送信
            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()
    # メッセージ送信(メッセージを送信するボタンがクリックされた時のイベントハンドラ)
    def send_message_click(e):
        # 新しいメッセージが空でないことをチェック
        if new_message.value != "":
            # メッセージを送信
            page.pubsub.send_all(Message(page.session.get("user_name"), new_message.value, message_type="chat_message"))
            # メッセージをクリアし、フォーカスを新しいメッセージ入力欄に移す
            new_message.value = ""
            new_message.focus()
            page.update()
    # メッセージを追加する(メッセージが届いた時のイベントハンドラ)
    def on_message(message: Message):
        # チャットメッセージを追加
        if message.message_type == "chat_message":
            m = ChatMessage(message)
        # ログインメッセージを追加
        elif message.message_type == "login_message":
            m = ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)
        # チャットコントロールにメッセージを追加
        chat.controls.append(m)
        page.update()
    # メッセージの受信を購読する(pubsub.send_allで発信されたメッセージを自動的に受け取ることができる)。
    page.pubsub.subscribe(on_message)   # 引数;受信時のイベントハンドラー
    # ユーザー名を入力するダイアログを作成
    join_user_name = ft.TextField(
        label="Enter your name to join the chat",
        autofocus=True,
        on_submit=join_chat_click,
    )
    page.dialog = ft.AlertDialog(
        open=True,
        modal=True,
        title=ft.Text("Welcome!"),
        content=ft.Column([join_user_name], width=300, height=70, tight=True),
        actions=[ft.ElevatedButton(text="Join chat", on_click=join_chat_click)],
        actions_alignment="end",
    )
    # チャットを表示するリストビューを作成
    chat = ft.ListView(
        expand=True,
        spacing=10,
        auto_scroll=True,
    )
    # チャットを入力するテキストフィールドを作成
    new_message = ft.TextField(
        hint_text="Write a message...",
        autofocus=True,
        shift_enter=True,
        min_lines=1,
        max_lines=5,
        filled=True,
        expand=True,
        on_submit=send_message_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,
                ),
            ]
        ),
    )
ft.app(port=8550, target=main, view=ft.WEB_BROWSER)メッセージクラスを定義
✅1つのメッセージに必要なデータをまとめたクラス。
# メッセージクラス
class Message():
    def __init__(self, user_name: str, text: str, message_type: str):
        self.user_name = user_name              # ユーザー名
        self.text = text                        # メッセージ
        self.message_type = message_type        # メッセージタイプ例:3つのメッセージの中身
チャット用コントロールクラスを定義
✅1回のチャットに必要なコントロール(画面のパーツ)をまとめたクラス。
# Rowを継承したコントロール
# チャットに必要なコントロールをまとめる
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, user_name: str):
        return user_name[:1].capitalize()
    # アバターの背景色を取得
    def get_avatar_color(self, user_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(user_name) % len(colors_lookup)]具体的にはチャットの1行にあたる部分(メッセージ、ユーザー名、アバター)をまとめている。
ユーザー名を登録するjoin_chat_clickを定義
最初にチャットを開いたときにユーザー名を入力する仕様になっている。
✅ここで「Join Chat」をクリックしたときこの関数が呼ばれる。(クリックイベントの登録は後述)
		# ユーザー名を登録(チャットに参加するボタンがクリックされた時のイベントハンドラ)
    def join_chat_click(e):
        # 参加ユーザー名が空の場合
        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", join_user_name.value)
            page.dialog.open = False
            # 新しいメッセージのプレフィックスを設定(ユーザー名の後に「:」を付ける」)
            new_message.prefix = ft.Text(f"{join_user_name.value}: ")
            # ログインメッセージを送信
            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()https://flet.dev/docs/guides/python/pub-sub/
ここでは
message_type="login_message"でログインしたことを通知している。メッセージを送信するsend_message_clickを定義
チャット画面の右下に送信ボタンがある。
✅この送信ボタンをクリックしたときにこの関数が呼ばれる。(クリックイベントの登録は後述)
		# メッセージ送信(メッセージを送信するボタンがクリックされた時のイベントハンドラ)
    def send_message_click(e):
        # 新しいメッセージが空でないことをチェック
        if new_message.value != "":
            # メッセージを送信
            page.pubsub.send_all(Message(page.session.get("user_name"), new_message.value, message_type="chat_message"))
            # メッセージをクリアし、フォーカスを新しいメッセージ入力欄に移す
            new_message.value = ""
            new_message.focus()
            page.update()https://flet.dev/docs/guides/python/pub-sub/
ここでは
message_type="chat_message"でチャットを送ったことを通知している。メッセージを追加するon_messageを定義
送信されたメッセージを画面に表示する処理が必要。
✅PubSubメッセージを受信したとき(ログインしたとき、チャットが送信されたとき)にこの関数が呼ばれる。
		# メッセージを追加する(メッセージが届いた時のイベントハンドラ)
    def on_message(message: Message):
        # チャットメッセージを追加
        if message.message_type == "chat_message":
            m = ChatMessage(message)
        # ログインメッセージを追加
        elif message.message_type == "login_message":
						# 【注意】このままだと文字が黒くて見ずらい。色を「ft.colors.WHITE」に変えるのがおすすめ。
            m = ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)
        # チャットコントロールにメッセージを追加
        chat.controls.append(m)
        page.update()
		# メッセージの受信を購読する(pubsub.send_allで発信されたメッセージを自動的に受け取ることができる)。
    page.pubsub.subscribe(on_message)   # 引数;受信時のイベントハンドラーpubsub.send_all関数でPubSubメッセージが送信される。message_typeは「ログイン用」と「チャット用」の2種類どちらかを受信する仕様。受信した種類に合わせて表示する内容を変えている。ログインしたとき(message_type="chat_message”を受信したとき)
チャットを送信したとき(message_type="login_message”を受信したとき)
(↑見やすくするため文字色を変えている)
ユーザー登録ダイアログを作成
アプリ起動時にユーザー名の入力ダイアログが表示される。
✅このダイアログを作成する
		# ユーザー名を入力するダイアログを作成
    join_user_name = ft.TextField(
        label="Enter your name to join the chat",
        autofocus=True,
        on_submit=join_chat_click,
    )
    page.dialog = ft.AlertDialog(
        open=True,
        modal=True,
        title=ft.Text("Welcome!"),
        content=ft.Column([join_user_name], width=300, height=70, tight=True),
        actions=[ft.ElevatedButton(text="Join chat", on_click=join_chat_click)],
        actions_alignment="end",
    )チャットを表示する部分を作成
ユーザー名登録後、チャット画面が表示される。
✅この画面上部のチャットを表示する部分。
		# チャットを表示するリストビューを作成
    chat = ft.ListView(
        expand=True,
        spacing=10,
        auto_scroll=True,
    )メッセージ入力欄を作成
✅同様に、画面下部のチャット入力欄。
		# チャットを入力するテキストフィールドを作成
    new_message = ft.TextField(
        hint_text="Write a message...",
        autofocus=True,
        shift_enter=True,
        min_lines=1,
        max_lines=5,
        filled=True,
        expand=True,
        on_submit=send_message_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,
                ),
            ]
        ),
    )ポイントまとめ
- たった1ファイルで簡易なチャットアプリが作れる。
- メッセージの送信にはPubSubを使う。
- それ以外は前回解説した基本知識だけで構成されている。














