boxmoe_header_banner_img

Hello! 欢迎来到悠悠畅享网!

文章导读

Flet 应用中利用 page.client_storage 实现数据持久化教程


avatar
站长 2025年8月7日 12

Flet 应用中利用 page.client_storage 实现数据持久化教程

本教程详细讲解如何在 Flet 应用中使用 page.client_storage 实现数据持久化,以确保用户数据在应用重启后依然存在。我们将深入探讨存储 Flet UI 控件时常见的“循环引用”错误,并提供正确的解决方案:即仅存储可序列化的数据类型(如字符串、数字或由它们组成的列表/字典),而非 Flet 控件对象本身,并通过示例代码演示如何有效管理和加载持久化数据。

1. 理解 Flet 的 page.client_storage

Flet 框架为开发者提供了一个名为 page.client_storage 的机制,用于在客户端(通常是浏览器或桌面应用的本地存储)持久化数据。这类似于 Web 开发中的 localStorage,它允许应用在会话结束后依然保留少量数据,以便在下次启动时恢复。

page.client_storage 是一个简单的键值对存储,其基本操作包括:

  • page.client_storage.set(key, value): 存储一个键值对。value 必须是可序列化的数据类型(字符串、数字、布尔值,或由它们组成的列表/字典)。
  • page.client_storage.get(key): 根据键获取存储的值。
  • page.client_storage.remove(key): 移除指定键的数据。
  • page.client_storage.clear(): 清除所有存储的数据。
  • page.client_storage.get_keys(prefix): 获取所有以指定前缀开头的键。

2. 为什么不能直接存储 Flet 控件?

在尝试使用 page.client_storage 存储 Flet UI 控件(如 ft.Row、ft.Text、ft.Checkbox 等)时,开发者经常会遇到 ValueError: Circular reference detected 或 ‘dict’ object has no attribute ‘_build_add_commands’ 等错误。

这是因为 Flet 控件是复杂的 Python 对象,它们内部包含对其他控件、父控件、事件处理器等的引用。这些复杂的内部结构导致它们无法直接被 JSON 序列化(page.client_storage 在底层通常依赖于 JSON 序列化来存储数据)。当你尝试存储一个包含循环引用的对象时,序列化器会陷入无限循环,从而抛出 Circular reference detected 错误。即使没有循环引用,Flet 控件对象本身也包含许多非数据属性和方法,这些都不是 client_storage 期望存储的简单数据。

核心原则:page.client_storage 应该存储数据,而不是 UI 控件。 你应该存储用户输入的数据(例如,待办事项的文本内容),然后在应用启动时,根据这些存储的数据动态地重建 UI 控件。

3. 实现一个持久化的 To-Do List 应用

我们将基于一个待办事项应用来演示如何正确使用 page.client_storage。

3.1 应用结构概览

我们的 To-Do List 应用将包含:

  • 一个文本输入框用于添加新任务。
  • 一个“添加”按钮。
  • 一个显示所有任务的区域。
  • 一个“清除所有任务”按钮。

3.2 关键实现步骤

  1. 数据存储策略:

    • 不存储 ft.Row 或 ft.Text 对象。
    • 只存储任务的文本内容(字符串)。
    • 为每个任务生成一个唯一的键,例如 t1, t2, t3 等,以便独立存储和检索。我们可以利用一个计数器来实现这一点。
  2. 添加任务与存储:

    • 当用户输入任务并点击“添加”按钮时,获取文本内容。
    • 更新一个计数器(例如,可以绑定在添加按钮的 data 属性上)。
    • 使用计数器作为键的一部分,将任务文本存储到 page.client_storage。
  3. 加载已存储任务:

    • 应用启动时,遍历 page.client_storage 中所有以特定前缀(例如“t”)开头的键。
    • 对于每个键,获取其对应的值(任务文本)。
    • 根据获取到的任务文本,动态创建 ft.Text 控件,并添加到任务列表中。
  4. 清除所有任务:

    • 当用户点击“清除所有任务”按钮时,调用 page.client_storage.clear() 清除所有数据。
    • 同时清空 UI 中的任务列表。

3.3 示例代码

import flet as ft  def main(page: ft.Page):     # 页面基本设置     page.window_center()     page.window_width = 400     page.window_height = 600     page.title = "Flet 持久化待办事项"     page.bgcolor = "#141414"      # 1. 定义清除所有任务的函数     def all_clear(e):         # 清除 client_storage 中的所有数据         page.client_storage.clear()         # 清除 UI 中的所有任务控件         task_column.controls.clear()         # 更新页面         page.update()      # 设置浮动操作按钮用于清除所有任务     page.floating_action_button = ft.FloatingActionButton(         icon=ft.icons.CLEANING_SERVICES_ROUNDED,         on_click=all_clear,         bgcolor="#4B90BE",         scale=0.95     )      # 2. 定义添加任务的函数     def add_task(e):         # 确保输入框不为空         if tf.value:             # 增加添加按钮的data属性作为任务的唯一ID计数器             # add_button.data 初始值为 0,每次点击增加             add_button.data += 1              # 获取任务文本             task_text = tf.value              # 创建一个 ft.Text 控件来显示任务             # 注意:这里我们创建了控件,但不会直接存储它             task_control = ft.Text(task_text, color=ft.colors.WHITE)              # 将任务控件添加到显示任务的列中             task_column.controls.append(task_control)              # 将任务文本存储到 client_storage             # 使用 f"task_{add_button.data}" 作为唯一的键             page.client_storage.set(f"task_{add_button.data}", task_text)              # 清空输入框             tf.value = ""              # 更新页面以显示新任务和清空的输入框             page.update()              # 打印当前所有存储的键,用于调试             print("当前存储的键:", page.client_storage.get_keys(''))          # 如果输入框为空,则不执行任何操作         else:             tf.focus() # 重新聚焦输入框             page.update()       # 任务输入框     tf = ft.TextField(         label='输入新任务',         expand=True, # 允许文本框扩展以填充可用空间         color=ft.colors.WHITE,         hint_text_style=ft.TextStyle(color=ft.colors.GREY_500)     )      # 添加任务按钮     # data=0 用于作为任务ID的计数器     add_button = ft.IconButton(         icon=ft.icons.ADD,         on_click=add_task,         data=0,         icon_color="#4B90BE",         tooltip="添加任务"     )      # 用于显示所有任务的列     task_column = ft.Column(         controls=[],         spacing=10 # 任务之间的间距     )      # 将输入框和添加按钮添加到页面顶部     page.add(         ft.Row(             controls=[                 tf,                 add_button             ],             alignment=ft.MainAxisAlignment.CENTER         )     )      # 将任务显示列添加到页面     page.add(         ft.Container(             content=task_column,             expand=True, # 允许任务列表扩展             padding=ft.padding.all(10),             alignment=ft.alignment.top_left         )     )      # 3. 应用启动时加载已存储的任务     # 检查 client_storage 中是否存在以 'task_' 开头的键     stored_keys = page.client_storage.get_keys('task_')     if stored_keys:         # 对键进行排序,以确保任务按添加顺序显示         # 提取数字部分进行排序,例如 'task_10' -> 10         sorted_keys = sorted(stored_keys, key=lambda k: int(k.split('_')[1]))          for key in sorted_keys:             # 获取存储的任务文本             task_text = page.client_storage.get(key)             if task_text: # 确保获取到的值不为空                 # 根据任务文本创建 ft.Text 控件                 task_control = ft.Text(task_text, color=ft.colors.WHITE)                 # 将控件添加到任务显示列中                 task_column.controls.append(task_control)                  # 更新 add_button.data,确保新的任务ID不会与现有ID冲突                 # 找到最大的ID,并将其设置为 add_button.data 的起始值                 current_max_id = int(key.split('_')[1])                 if current_max_id > add_button.data:                     add_button.data = current_max_id          # 加载完成后,将 add_button.data 增加1,为下一个新任务做准备         add_button.data += 1          # 更新页面以显示加载的任务         page.update()   # 运行 Flet 应用 ft.app(target=main)

3.4 注意事项与改进

  1. 唯一键管理: 在上述示例中,我们使用 add_button.data 作为计数器来生成唯一的任务键(例如 task_1, task_2)。这是一种简单有效的方法。在应用启动时,需要遍历所有已存储的键,找出最大的数字,并更新 add_button.data,以确保新添加的任务不会覆盖现有任务。

  2. 单个任务的删除: 示例中只实现了“清除所有任务”。如果需要删除单个任务,则在创建每个任务的 UI 控件时,需要将其对应的 client_storage 键关联起来(例如,通过 control.data 属性)。当删除按钮被点击时,可以根据这个键从 client_storage 中移除对应的数据,并从 UI 中移除该控件。

  3. 更复杂的数据结构: 如果需要存储更复杂的数据(例如,任务的文本、完成状态、截止日期等),可以考虑将每个任务存储为一个字典,然后将所有任务的字典列表进行 JSON 序列化,作为一个单一的值存储到 client_storage 中。

    import json # 存储 tasks_data = [{"text": "Buy milk", "completed": False}, {"text": "Learn Flet", "completed": True}] page.client_storage.set("all_tasks", json.dumps(tasks_data))  # 读取 stored_json = page.client_storage.get("all_tasks") if stored_json:     loaded_tasks = json.loads(stored_json)     # 根据 loaded_tasks 重新构建 UI

    这种方式更易于管理和更新,因为它将所有相关数据存储在一个条目下。

4. 总结

page.client_storage 是 Flet 应用实现数据持久化的强大工具,但正确使用它至关重要。核心在于理解它是一个键值对存储,只能存储可序列化的数据,而非复杂的 Flet UI 控件对象。通过将数据与 UI 逻辑分离,并在应用启动时动态重建 UI,我们可以有效地利用 client_storage 来提升用户体验,确保应用数据的持久性。



评论(已关闭)

评论已关闭