news 2026/5/4 23:52:15

Day2_开源鸿蒙_Flutter_for_OpenHarmony_离线笔记_自动保存与搜索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day2_开源鸿蒙_Flutter_for_OpenHarmony_离线笔记_自动保存与搜索

开源鸿蒙 Flutter for OpenHarmony:sqflite搜索+自动保存

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Day2 的重点放在一件事:把 sqflite 用得更像“真实笔记应用”

  • 笔记多了要能搜:用sqflite做标题/正文关键字查询(LIKE)
  • 写笔记要安全:输入过程中自动落盘,返回前再兜底保存一次(底层还是sqflite的 insert/update)

本篇不新增第三方库,沿用 Day1 的三方库组合:sqflite + fluttertoast + path


1. 今天用到的第三方库(各自负责什么)

🧩 sqflite

  • 负责:SQLite 查询/插入/更新(搜索、自动保存最终都是它在写库/查库)

🧩 fluttertoast

  • 负责:把“保存成功/保存失败”这种状态给用户一个轻提示(不挡操作)

🧩 path

  • 负责:拼接数据库文件路径(Day1 已完成,本篇继续沿用)

2. 搜索怎么写:sqflite 的 query + LIKE + whereArgs

搜索的目标很简单:

  • 只搜未删除数据:is_deleted = 0
  • 标题或正文命中即可:title LIKE ? OR content LIKE ?
  • 最近更新的排前面:orderBy: 'updated_at DESC'

📌 文件:lib/features/note/data/note_dao.dart

Future<List<Note>>searchNotes(Stringkeyword,{int limit=100})async{finaldb=await_db.database;finalk='%${keyword.trim()}%';finalrows=awaitdb.query('notes',where:'is_deleted = ? AND (title LIKE ? OR content LIKE ?)',whereArgs:[0,k,k],orderBy:'updated_at DESC',limit:limit,);returnrows.map(_fromRow).toList(growable:false);}

这段代码专门讲清楚 4 个点(学会就能举一反三):

✅ 1)为什么用db.query(...)

  • query是 sqflite 的高频接口:表名、where、排序、limit 都是参数,不需要自己拼 SQL 字符串

✅ 2)为什么whereArgs必须用

  • 不要把关键字直接拼到where里(容易出错,也不利于排查)
  • whereArgs就是参数绑定:title LIKE ??对应k

✅ 3)为什么k要写成%关键字%

  • LIKE的包含匹配写法:%表示任意字符串
  • 如果只想前缀匹配,可以用关键字%

✅ 4)为什么要orderBy updated_at DESC

  • 笔记类列表通常“最近编辑的在前面”,搜索结果也一样

📷 截图位(建议准备 3 张)



3. 搜索框怎么写:避免每个字都触发一次 sqflite 查询

如果输入一个字就查一次库,sqflite的查询会变得很频繁,体验会抖。处理方式很朴素:防抖,停 300ms 再查。

📌 文件:lib/features/note/ui/notes_list_page.dart

Timer?_searchDebounce;latefinalTextEditingController_searchController;Future<List<Note>>_loadNotes(){finalkeyword=_searchController.text.trim();if(keyword.isEmpty){return_repo.listNotes();}return_repo.searchNotes(keyword);}void_onSearchChanged(String_){_searchDebounce?.cancel();_searchDebounce=Timer(constDuration(milliseconds:300),(){if(!mounted)return;_reload();});}

这里和sqflite的关系是:

  • _repo.searchNotes(keyword)最终会走到NoteDao.searchNotes(...)
  • 防抖的意义就是“减少 sqflite 的 query 调用次数”

4. 自动保存怎么写:核心是把 sqflite 的 insert/update 调用节奏做对

自动保存不是“疯狂写库”,目标是两条:

  1. 输入停下来一小段时间,落盘一次(防抖)
  2. 返回页面前,再兜底落盘一次

这背后对应的就是 sqflite 的两类写操作:

  • 第一次:insert(新建一条 note)
  • 后续:update(持续更新同一条 note)

4.1 防抖保存:800ms 不输入就写一次库

📌 文件:lib/features/note/ui/note_editor_page.dart

Timer?_autoSaveDebounce;bool _dirty=false;void_scheduleAutoSave(){_dirty=true;_autoSaveDebounce?.cancel();_autoSaveDebounce=Timer(constDuration(milliseconds:800),()async{if(!mounted)return;await_persist(showToastOnEmpty:false,showToastOnSuccess:false);});}

这段写法的好处:

  • 不是每次输入都写库,而是“停下来再写”
  • sqflite写库次数减少,体验更稳

4.2 持久化函数:把 create/update 封装成一个入口

📌 文件:lib/features/note/ui/note_editor_page.dart

bool _saving=false;bool _queuedSave=false;Note?_note;Future<void>_persist({required bool showToastOnEmpty,required bool showToastOnSuccess,})async{if(!_dirty&&_note!=null)return;if(_saving){_queuedSave=true;return;}finaltitle=_titleController.text;finalcontent=_contentController.text;if(title.trim().isEmpty&&content.trim().isEmpty){if(showToastOnEmpty)awaitshowToast('内容为空,未保存');return;}setState(()=>_saving=true);try{finalexisting=_note;if(existing==null){finalcreated=awaitwidget.repo.create(title:title,content:content);_note=created;}else{awaitwidget.repo.update(id:existing.id!,title:title,content:content,createdAt:existing.createdAt,);}_dirty=false;if(showToastOnSuccess)awaitshowToast('已保存');}catch(e){awaitshowToast('保存失败:$e');}finally{if(mounted)setState(()=>_saving=false);if(mounted&&_queuedSave){_queuedSave=false;await_persist(showToastOnEmpty:false,showToastOnSuccess:false);}else{_queuedSave=false;}}}

这里最关键的是:它把 sqflite 的写库“节奏”控制住了。

✅ 1)第一次自动保存为什么能成功

  • existing == null时走repo.create(...)
  • create最终会走到 DAO 的insert(...)(sqflite insert)
  • 插入成功后把_note赋值,后面就不会重复 insert

✅ 2)为什么不会并发写库

  • _saving期间如果又来了保存请求,就把_queuedSave标记为 true
  • 本轮保存结束后再跑下一轮 persist(串行写库)

✅ 3)fluttertoast 在这里怎么用

  • 自动保存默认不弹“已保存”,避免打扰
  • 只有点击保存按钮时才showToast('已保存')
  • 失败一定 toast(否则用户以为保存了,其实没写进去)

5. 返回兜底保存:确保最后一段输入不会丢

📌 文件:lib/features/note/ui/note_editor_page.dart

returnPopScope(canPop:false,onPopInvokedWithResult:(didPop,result){if(didPop)return;_onBackPressed();},child:Scaffold(appBar:AppBar(leading:IconButton(onPressed:_onBackPressed,icon:constIcon(Icons.arrow_back),),),),);

_onBackPressed()做的事很直接:取消防抖计时器 → 如果脏数据未落盘则 persist 一次 → 再退出页面。

📷 截图位(建议准备 2 张)

![Day2-编辑输入中](图_day2_editing.png) ![Day2-自动保存后列表更新时间变化](图_day2_autosave_list.png)

6. 自测清单(Day2)

🧪 搜索:

  • 输入关键字能命中标题/正文
  • 清空关键字后恢复完整列表
  • 连续输入时不卡顿(防抖生效)

🧪 自动保存:

  • 新建页输入一段内容,不点保存直接返回 → 再进列表仍能看到内容
  • 编辑已有笔记,停 1 秒左右返回 → 列表更新时间变更
  • 快速连续输入时不会卡死/不会报错(串行写库生效)

7. 下一步(Day3 方向)

Day3 适合写一篇更“排错向”的内容:sqflite 锁库、并发写入、迁移失败如何复现与定位。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/4 23:41:32

Taotoken 用量看板如何帮助团队分析并优化大模型 API 支出

Taotoken 用量看板如何帮助团队分析并优化大模型 API 支出 1. 用量数据的可视化呈现 Taotoken 平台为团队提供了多维度的用量数据可视化功能。控制台的用量看板默认展示近30天的Token消耗趋势图&#xff0c;可按自然日或小时粒度切换视图。图表支持按模型供应商、模型版本、A…

作者头像 李华
网站建设 2026/5/4 23:41:32

AI辅助开发新范式:通过快马平台让Hermes Agent成为你的代码审查员

今天想和大家分享一个有趣的开发体验&#xff1a;如何用AI来辅助代码优化。最近在InsCode(快马)平台尝试了Hermes Agent这个工具&#xff0c;发现它不仅能直接生成代码&#xff0c;还能扮演"代码审查员"的角色&#xff0c;帮我们找出代码中的潜在问题。整个过程特别适…

作者头像 李华
网站建设 2026/5/4 23:37:01

Kimi K2.6:面向生产级智能体的万亿参数 MoE 架构解析

月之暗面&#xff08;Moonshot AI&#xff09;发布的Kimi K2.6&#xff0c;是一款专为智能体编程场景打造的万亿参数级 MoE 模型。基于前代 K2 系列的架构迭代&#xff0c;K2.6 在智能体集群协作、长上下文代码处理、多模态理解等能力上实现了系统性升级&#xff0c;在 SWE-Ben…

作者头像 李华
网站建设 2026/5/4 23:30:27

从CT原始数据到3D结节检测模型:一份给医学图像新手的Luna16预处理与FROC评估全流程拆解

从CT原始数据到3D结节检测模型&#xff1a;医学图像处理全流程实战指南 第一次接触医学图像分析时&#xff0c;我被那些复杂的文件格式和专业术语搞得晕头转向。记得当时盯着电脑屏幕上的.mhd和.raw文件发呆&#xff0c;完全不知道如何将它们转换成可用的数据格式。如果你现在也…

作者头像 李华
网站建设 2026/5/4 23:29:30

别再手动解析NMEA了!用开源nmealib库提升你的STM32 GPS项目效率

STM32 GPS开发实战&#xff1a;从NMEA协议解析到nmealib高效应用 在嵌入式GPS开发中&#xff0c;NMEA协议的解析一直是让开发者头疼的问题。手动解析不仅代码量大、容易出错&#xff0c;还难以应对各种异常情况。我曾在一个农业无人机项目中&#xff0c;因为NMEA解析的bug导致定…

作者头像 李华