使用Flutter UI在Python中创建实时聊天应用程序
#网络开发人员 #教程 #python #pubsub

在本教程中,我们将使用Flet framework在Python中使用Flutter UI创建一个微不足道的内存聊天应用程序。该应用程序可能是创建自己更复杂和有用的项目的好起点。

在本教程中,您将学习如何:

完整的应用程序看起来像这样:

Image description

您可以使用现场演示here

可以找到聊天应用程序的完整代码。

Flet入门

要创建一个Flet Web应用程序,您不需要了解HTML,CSS或JavaScript,但是您确实需要对Python和面向对象的编程的基本知识。

flet需要python 3.7或更高。要在Python中创建Flet应用程序,您需要先安装flet模块:

pip install flet

注意:升级flet
升级flet模块,运行:

pip install flet --upgrade

创建带有以下内容的hello.py

import flet as ft

def main(page: ft.Page):
    page.add(ft.Text(value="Hello, world!"))

ft.app(target=main)

运行此应用程序,您将看到一个带有问候的新窗口:

Image description

添加页面控件和处理活动

开始,我们希望能够获取用户输入(聊天消息)并在屏幕上显示消息历史记录。此步骤的布局看起来像这样:

Image description

要实现此布局,我们将使用这些Flet控件:

  • koude4-一个容器垂直显示聊天消息(Text控件)。
  • koude5-聊天中显示的聊天消息。
  • koude8-用于从用户获取新消息输入的输入控件。
  • koude9-“发送”按钮将添加新消息到chat Column
  • koude11-一个水平显示TextFieldElevatedButton的容器。

创建带有以下内容的chat.py

import flet as ft

def main(page: ft.Page):
    chat = ft.Column()
    new_message = ft.TextField()

    def send_click(e):
        chat.controls.append(ft.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("chat", target=main, view=ft.WEB_BROWSER)

当用户单击“发送”按钮时,它会触发调用send_click方法的on_click事件。然后,send_click将新的Text控件添加到列controls列表中,并清除new_message textfield值。

重要
更新控件的任何属性后,应要求对控件的update()方法(或其父控件)进行更新。

聊天应用现在看起来像这样:

Image description

广播聊天消息

在上一步中,我们创建了一个简单的Web应用程序,该应用程序从用户中获取输入并在屏幕上显示聊天消息。如果您在两个Web浏览器选项卡中打开此应用程序,它将创建两个应用程序会话。每个会话都会有自己的消息列表。

要构建实时聊天应用程序,您需要以某种方式传递聊天应用程序会话之间的消息。当用户发送消息时,应将其广播到所有其他应用程序会话并在其页面上显示。

flet提供了一种简单的内置PubSub在页面之间的异步通信的机制。

首先,我们需要订阅用户接收广播消息:

    page.pubsub.subscribe(on_message)

pubsub.subsribe()方法将将当前的应用程序会话添加到订户列表中。它接受handler作为一个参数,后来将在发布者调用pubsub.send_all()方法的那一刻被调用。

handler中,我们将在chat controls列表中添加新消息(Text):

    def on_message(message: Message):
        chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
        page.update()

最后,当用户单击“发送”按钮时,您需要调用pubsub.send_all()方法:

    def send_click(e):
        page.pubsub.send_all(Message(user=page.session_id, text=new_message.value))
        new_message.value = ""
        page.update()

    page.add(chat, ft.Row([new_message, ft.ElevatedButton("Send", on_click=send_click)]))   

pubsub.send_all()将调用on_message()并将消息对象传递给它。

这是此步骤的完整代码:

import flet as ft

class Message():
    def __init__(self, user: str, text: str):
        self.user = user
        self.text = text

def main(page: ft.Page):

    chat = ft.Column()
    new_message = ft.TextField()

    def on_message(message: Message):
        chat.controls.append(ft.Text(f"{message.user}: {message.text}"))
        page.update()

    page.pubsub.subscribe(on_message)

    def send_click(e):
        page.pubsub.send_all(Message(user=page.session_id, text=new_message.value))
        new_message.value = ""
        page.update()

    page.add(chat, ft.Row([new_message, ft.ElevatedButton("Send", on_click=send_click)]))

ft.app(target=main, view=ft.WEB_BROWSER)

Image description

用户名对话框

您在上一步中创建的聊天应用程序具有在用户会话之间交换消息所需的基本功能。不过,它不是很友好的,因为它显示了发送消息的session_id,这并没有说明与您交流的人。

让我们改进我们的应用程序以显示每个消息的用户名而不是session_id。要捕获用户名,我们将使用koude33控件。让我们添加到页面:

    user_name = ft.TextField(label="Enter your name")

    page.dialog = ft.AlertDialog(
        open=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",
    )

注意
由于我们已将其open属性设置为True

,将在程序开始时打开对话框。

Image description

用户单击“加入聊天”按钮时,它将调用join_click方法,该方法应向所有订户发送消息,并通知他们用户已加入聊天。此消息应该与常规聊天消息不同,例如:

Image description

让我们将message_type属性添加到Message类中以区分登录和聊天消息:

class Message():
    def __init__(self, user: str, text: str, message_type: str):
        self.user = user
        self.text = text
        self.message_type = message_type

我们将在on_message方法中检查message_type

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()

“ login_message”和“ chat_message”类型的消息现在将在两个事件上发送:当用户加入聊天和用户发送消息时。

让我们创建join_click方法:

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 session storage存储User_name将其未来用途存储在send_click方法中发送聊天消息。

注意
用户名对话框将在我们将其open属性设置为False并致电update()方法后立即关闭。

最后,让我们更新send_click方法使用以前使用page.session保存的user_name

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()

可以找到此步骤的完整代码here

Image description

增强用户界面

您在上一步中创建的聊天应用程序已经达到了其在用户之间具有基本登录功能的用户之间的目的。

在转到deploying your app之前,我们建议在其中添加一些额外功能,以改善用户体验并使应用程序看起来更专业。

可重复使用的用户控件

您可能想以不同的格式显示消息:

Image description

聊天消息现在将是包含用户名缩写的koude50Row和包含两个Text控件的Column:用户名和消息文本。

我们需要在聊天应用程序中显示很多聊天消息,因此创建自己的可重复使用的控件是有意义的。让我们创建一个将从Row继承的新的ChatMessage类。

创建ChatMessage类实例时,我们将通过一个参数传递Message对象,然后ChatMessage将根据message.user_namemessage.text显示自身显示:

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)]

ChatMessage控件提取缩写和算法从用户名衍生出头像。
稍后,如果您决定改善控制布局或其逻辑,它不会影响程序的其余部分 - 这就是封装的力量!

布置控件

现在,您可以使用全新的ChatMessage为聊天应用构建更好的布局:

Image description

将创建ChatMessage的实例,而不是在on_message中的普通Text聊天消息:

    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()

新布局建议的其他改进是:

  • koude65而不是用于显示消息的Column,以便稍后浏览消息
  • Container用于displayng边界围绕ListView
  • koude69而不是ElevatedButton发送消息
  • 使用koude71属性进行控件填充可用空间

这是您可以实现此布局的方式:

    # Chat messages
    chat = ft.ListView(
        expand=True,
        spacing=10,
        auto_scroll=True,
    )

    # A new message entry form
    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,
    )

    # Add everything to the page
    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,
                ),
            ]
        ),
    )

可以找到此步骤的完整代码。

这是本教程目的的聊天应用程序的最终版本。您可以在下面阅读有关我们所做的增强功能的更多信息。

键盘支持

聚焦输入控件

所有数据输入控件都具有autofocus属性,将其设置为True时,将初始焦点移至控件。如果带有autofocus集的页面上有多个控件,则添加到页面的第一个将获得焦点。

我们在对话框内部的用户名Textfield上设置了autofocus=True,然后在Textfield上设置了聊天消息,以在对话框关闭时将其设置为初始焦点。

用户单击“发送”按钮或按Enters提交聊天消息时,Textfield失去了焦点。
为了编程设置控制焦点,我们使用了koude76方法。

Enter上提交表格

,只需在键盘上按下Enter按钮即可提交表格!在对话框中键入您的名字,点击Enter,键入新消息,点击Enter,键入另一个,键入Enter-根本不涉及鼠标! ð

flet可以通过提供koude82事件处理程序来支持,该事件处理程序在用户按Enter按钮时发射焦点。

输入多行消息

多行文本菲尔德又如何将光标推向下一行?我们也涵盖了! TextField控件具有koude86属性,将其设置为True时,启用类似Discord的行为:到达新线路用户时按Shift+Enter,同时击中Enter时会提交表单。

>

动画滚动到最后一条消息

注意到滚动到聊天窗口中的最后一条消息的好动画吗?可以通过将koude91属性设置为True来启用。最高的Page类是可滚动容器本身,也支持koude94

页面标题

最终触摸 - 可以简单地更改为:
的页面标题

page.title = "Flet Chat"
page.update()

部署应用程序

恭喜!您已经用Flet创建了python的聊天应用程序,看起来很棒!

现在是时候与世界分享您的应用程序了!

Follow these instructions将您的flet应用程序部署为fly.io或reveit的Web应用程序。

下一步是什么

我们可以实现许多功能来改进此聊天应用程序:

  • 断开,重新连接,会话超时
  • 上传/下载图像
  • 身份验证,头像
  • 使用数据库进行存储
  • 聊天频道,主题
  • 全文搜索
  • 表情符号,Markdown
  • 机器人
  • 移动应用

如果您想为应用程序/教程做出贡献并与其他Flet开发人员分享。

概括

在本教程中,您学会了如何:

  • 创建一个简单的flet应用;
  • 添加页面控件和处理事件;
  • 使用PubSub库中的使用;
  • 输入用户名的用户alertdialog;
  • 使用可重复使用的控件构建页面布局;
  • 将您的Flet应用程序部署到网络;

要进一步阅读,您可以探索controlsexamples repository

我们很想听听您的反馈!请给我们一个email,加入Discord的讨论,关注Twitter