flutter duration flutter 持久化数据
本教程详细讲解如何在 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 detectors 或 'dict' object has no attribute '_build_add_commands' 等错误。
这是因为 Flet 控件是复杂的 Python对象,它们内部包含对其他控件、父控件、事件处理器等的引用。这些复杂的内部结构导致它们无法直接被 JSON 序列化(page.client_storage 在底层通常依赖于 JSON 序列化来存储数据)。当你尝试存储一个包含循环引用的对象时,序列化器会陷入无限循环,从而引发循环引用检测错误。即使没有循环引用,Flet 控件对象本身也包含许多非数据属性和方法,这些都不是 client_storage希望存储的简单数据。
核心原则:page.client_storage应该存储数据,而不是UI控件。 你应该存储用户输入的数据(例如,待办的文本内容),然后在应用程序启动时,根据这些存储事项的数据动态地重建UI控件。3. 实现持久化的待办事项列表应用
我们将基于一个待办事项应用来演示如何正确使用 page.client_storage。3.1 应用结构概述
我们的待办事项列表应用程序将包含:一个用于新任务的文本输入框。一个“添加”按钮。
一个显示所有任务的区域。一个“清除所有任务”按钮。3.2关键实现步骤
存储数据策略:不存储 ft.Row 或 ft.Text 对象。只存储任务的文本内容(字符串)。为每个任务生成一个唯一的键,例如 t1,t2,t3等,促进独立存储和搜索。我们可以利用一个投票来实现点。
添加任务与存储:当用户输入任务并点击“添加”按钮时,获取文本内容。更新一个投票(例如,绑定在添加按钮的数据可以属性上)。使用投票作为按钮的一部分,将任务存储文本到page.client_storage。
此加载已存储任务:应用启动时,导出page.client_storage中所有以特定某个(例如“t”)的键。对于每个键,获取其对应的值(任务文本)。根据获取到的任务文本,动态创建ft.Text控件,并添加到任务列表中。
清除所有任务:当用户点击“清除任务”按钮时,调用 page.client_storage.clear() 清除所有数据。同时清空 UI 中的任务列表。
3.3 示例代码import flet as ftdef main(page: ft.Page): # 页面基本设置 page.window_center() page.window_width = 400 page.window_height = 600 page.title = quot;Flet 持久化待办事项quot; page.bgcolor = quot;#141414quot; # 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=quot;#4B90BEquot;,scale=0.95 ) # 2. 添加任务的函数 def add_task(e): # 确保输入框不为空 if tf.value: # 增加添加按钮的数据属性作为任务的唯一 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 # 使用 fquot;task_{add_button.data}quot;作为唯一的键 page.client_storage.set(fquot;task_{add_button.data}quot;, task_text) # 清空输入框 tf.value = quot;quot;# 更新页面以显示新任务和清空的输入框 page.update() # 打印当前所有存储的键
,用于调试 print(quot;当前存储的键:quot;, 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=quot;#4B90BEquot;, tooltip=quot;添加任务quot; ) # 用于显示任务所有的列 task_column = ft.Column( 控件=[],间距=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_') ifstored_keys: # 对键添加排序,以确保任务按显示顺序 # 取出数字部分进行排序,例如 'task_10' -gt; 10sorted_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 gt; add_button.data: add_button.data = current_max_id # 加载完成后,将 add_button.data 增加 1,为下一个新任务做准备 add_button.data = 1 # updatePages 显示加载的任务 page.以()# 运行 Flet应用ft.app(target=main)登录后复制3.4 注意事项与改进
唯一键管理:在上述示例中,我们使用add_button.data作为计数器来生成唯一的任务键(例如task_1,task_2)。这是一种简单有效的方法。在应用启动时,需要遍历所有已存储的键,找出最大的数字,并更新add_button.data,以确保新添加的任务不会覆盖现有任务。
单个任务的删除:示例中只实现了“清除所有任务”。如果需要删除单个任务,则在创建任务每个的 UI 控件时,需要将其对应的 client_storage 键关联起来(例如,通过 control.data 属性)。当删除按钮被点击时,可以根据这个键从 client_storage 中删除对应的数据,并从 UI 中删除该控件。
更复杂的数据结构:如果需要存储更复杂的数据(例如,任务的文本、完成状态、初始化日期等),可以考虑将每个任务存储为一个字典,然后将所有任务的字典列表进行 JSON 序列化,作为一个单一的值存储到 client_storage 中。
import json# 存储tasks_data = [{quot;textquot;: quot;买牛奶quot;, quot;completedquot;: False}, {quot;textquot;: quot;学习Fletquot;, quot;completedquot;: True}]page.client_storage.set(quot;all_tasksquot;, json.dumps(tasks_data))# 读取stored_json = page.client_storage.get(quot;all_tasksquot;)ifstored_json:loaded_tasks = json.loads(stored_json)#根据loaded_tasks重新构建UI登录后复制
这种方式更容易管理和更新,因为会将所有相关数据存储放在一个连接下。4. 总结
page.client_storage是Flet应用实现数据持久化的强力工具,但正确使用它至关重要。核心定位理解它是一个键值对存储,只能存储可序列化的数据,而不是复杂的Flet UI控件对象。通过将数据与UI逻辑分离,并在应用启动时动态重建UI,我们有效地利用client_storage来提升用户体验,保证应用数据的持久性。
以上就是Flet应用中的利用page.client_storage实现数据持久化教程的详细内容,更多请关注乐哥常识网其他相关文章!