[🛠] Keymory 开发日志 #7:解决 FleatherViewer 问题,解决 HomeFeedPage Unexpected null value 问题
✨ GPT 的摘要
解决 FleatherViewer 的更新问题,并通过改进 HomeFeedPage 的过滤方式完成问题修复与性能优化。也因此学到使用 Conditional Visibility 代替 Query filter 很有效。
💻 开发日志
⏰ 今天要做的事
- ✅ (问题)HomeFeedPage - FeedCardDiary:在 ListView 中改变日期时,只有 FleatherViewerWidget 的值不会变化。
- 看起来 Widget 的 initialDeltaJson 值没有在每次 Diary Document 改变时更新。
-
刚问 o1 就一次解决。
/// didUpdateWidget: /// - 在同一个 widget tree 中只是参数变化时,Flutter 会复用已有的 State。 /// - 这时可以比较新的 widget 属性和旧的 widget 属性,并执行所需逻辑。 @override void didUpdateWidget(covariant FleatherViewerWidget oldWidget) { super.didUpdateWidget(oldWidget); // 1) 如果旧 JSON 和新 JSON 不同 if (oldWidget.initialDeltaJson != widget.initialDeltaJson) { // 2) 重新创建 FleatherController _controller?.dispose(); _controller = _createControllerFromDeltaJson(widget.initialDeltaJson); // 3) 刷新 UI setState(() {}); } } /// 从 Delta JSON 字符串创建 FleatherController 的 helper 函数 FleatherController _createControllerFromDeltaJson(String jsonString) { ParchmentDocument doc; try { final decoded = jsonDecode(jsonString); doc = ParchmentDocument.fromJson(decoded); } catch (e) { // 还原失败时,按普通文本处理 doc = ParchmentDocument.fromDelta( Delta()..insert(_ensureEndsWithNewline(jsonString)), ); } return FleatherController(document: doc); }
- ✅ (问题)HomeFeedPage:On page load 时 Unexpected Null Value 短暂出现后消失。
- 确认问题出在 HomeFeedPage 的 ListView 上设置的
日期过滤。 - 试着打开
ListView的Backend Query选项里的 Filter On Null Values。- Filter On Null Values:By default, if the value of any filter is null, the query will ignore the filter for that field. Checking this will instead keep null filters.
- Backend Query - Query Collection 修改尝试 1(失败)
- Filter On Null Values:ON
- Filter 1:
created_time,Equal To,getFirstTimeOfTheDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay - Order by:
created_time,Decreasing - 无法完成 Backend Query 设置
- Error message:You can’t order your query by a field used in a filter using
==orin.
- Error message:You can’t order your query by a field used in a filter using
- Backend Query - Query Collection 修改尝试 2(失败)
- Filter On Null Values:ON
- Filter 1:
created_time,Greater Than or Equal To,getFirstTimeOfTheDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay - Order by:
created_time,Decreasing - 可以完成 Backend Query 设置
-
进入 debugging mode 时出现下面的错误,页面无法创建。
Assertion failed: file:/// opt/ - pub-cache/hosted/pub.dev/ cloud_firestore-5.5.0/lib/src/ query dart: 650:9 conditions .where ((List<dynamic> item) => equality.equals(condition, item)) .isEmpty "Condition [FieldPath([created_time]), !=, null] already exists in this query." The relevant error-causing widget was: HomeFeedWidget
- Backend Query - Query Collection 修改尝试 3(失败)
- Filter On Null Values:ON
- Filter 1:
created_time,Greater Than,getLastTimeOfPrevDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay - Order by:
created_time,Decreasing - 可以完成 Backend Query 设置
-
进入 debugging mode 时出现下面的错误,页面无法创建。
Assertion failed: file:/// opt/ - pub-cache/hosted/pub.dev/ cloud_firestore-5.5.0/lib/src/ query dart: 650:9 conditions .where ((List<dynamic> item) => equality.equals(condition, item)) .isEmpty "Condition [FieldPath([created_time]), !=, null] already exists in this query." The relevant error-causing widget was: HomeFeedWidget
- Backend Query - Query Collection 修改尝试 4(失败)
- Filter On Null Values:OFF
- Filter 1:
created_time,Greater Than,getLastTimeOfPrevDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay - Order by:
created_time,Decreasing - 可以完成 Backend Query 设置
-
进入 debugging mode 时,下面的错误会短暂出现后消失。
Unexpected Null value. The relevant error-causing widget was: HomeFeedWidget
- (问 o1)为什么 Unexpected Null Value 会短暂出现?
- FlutterFlow(或者内部代码)很可能在页面初始加载时,让 getLastTimeOfPrevDay() 或 getFirstTimeOfNextDay() 等返回了 null。(比如 date 还没初始化之类……)
- 如果打开 Filter On Null Values,内部会额外添加类似 != null 的条件,而加载瞬间传入 null 时就可能发生冲突。
- 所以才会出现“短暂弹出 null error -> 被实际值替换 -> 加载结束后消失”的现象。
- Backend Query - Query Collection 修改尝试 5(成功!)
- Filter On Null Values:OFF
- Order by:
created_time,Decreasing - Component Widget Conditional Visibility 设置
- Filter 1:
created_time,Greater Than,getLastTimeOfPrevDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay
- Filter 1:
- 而且作为额外收获,ListView 加载卡顿(Latency)也消失了。看起来是因为同时给 Query 两个 Filter 和一个 Order,耗费了不少时间。
- 💡 学到的点
- 使用 Backend Query 时,最好尽量排除 filter,用 Conditional Visibility 来控制。
- 这其实是前期问过导师并得到过回答的问题,但当时只是脑子里大概理解就过去了,结果最后还是忘掉。亲自折腾过之后,现在才算真正明白。
- 确认问题出在 HomeFeedPage 的 ListView 上设置的
- ✅ (问题)DiaryPage:日期总是保存为今天
- 创建 DiaryPage - dateFocused 参数
- 将 HomeFeedPage 的 dateToShow 值作为 dateFocused 参数传递。
💯 已完成事项摘要
FleatherViewer 更新问题解决
- 使用 didUpdateWidget,让 FleatherViewer 在 Diary Document 每次变化时都能正常更新。 HomeFeedPage 过滤优化
使用 Conditional Visibility 代替 Backend Query 的过滤方式,提高性能。
- 通过最小化 Query filtering 改善加载速度。
DiaryPage 日期保存错误修正
- 从 HomeFeedPage 向 DiaryPage 传递 dateFocused 值,让创建新日记时能够反映所选日期。
🎯 以后要做的事
点击查看详情
- ❔ (问题)DiaryPage:使用 markdown viewer(防止溢出:Container Height)
- ❔ (问题)HomeFeedPage - FeedCardDiary:正确应用 AI Comment 角色图片
- ❔ DiaryPage - AI Comment:Save 时,也把撰写评论的角色 Ref 保存到 Diary。
- ❔ HomeFeedPage - FeedCardDiary - AI Image:用 Backend Query 加载角色 Doc,并指定 Image Path。
-
❔ DiaryPage:根据 Mood Slider 值改变脸部表情 Emoji
- ❔ Chat Page - Create New Chat:应用 system prompt
- ▶️ OpenAI API Call 实现:createChatCompletion
- Input:System Prompt(Chat)
- Output:New Chat Message by AI($.choices[0].message.content)
- Additional Actions:Create New Chat, Create New Message
- ▶️ OpenAI API Call 实现:createChatCompletion
- ❔ DiaryPage - Create New Chat by Diary:基于日记内容创建 New Chat
- ❔ OpenAI API Call 实现:createDiaryComment
- Input:Diary Content, System Prompt(AI Comment)
- Output:AI Comment($.choices[0].message.content)
- ❔ OpenAI API Call 实现:createDiarySummary
- Input:Diary Content, AI Comment, System Prompt(Diary Summary)
- Output:Diary Summary($.choices[0].message.content)
- ❔ OpenAI API Call 实现:createChatFromDiary
- Input:Diary Summary, System Prompt(Chat From Diary)
- Output:New Chat Message by AI($.choices[0].message.content)
- Additional Actions:Create New Chat, Create New Message
- ❔ OpenAI API Call 实现:createDiaryComment
- ❔ ChatPage - Create New Diary:基于对话内容创建 New Diary
- ❔ OpenAI API Call 实现:createChatSummary
- Input:Chat Content, System Prompt(Diary From Chat)
- Output:Chat Summary
- ❔ OpenAI API Call 实现:createChatSummary
- ❔ ChatPage - Create New Diary:基于对话内容创建 New Diary
- ❔ OpenAI API Call 实现:createDiaryFromChat
- Input:Chat Summary, System Prompt(Diary From Chat)
- Output:New Diary(Title, Content, Mood score)
- ❔ OpenAI API Call 实现:createDiaryFromChat
-
❔ ChatPage:Go to Linked Diary
- ❔ HomeFeedPage - import 后应用 flutter_slidable:4.0.0
-
❔ HomeFeedPage:在 FeedCardDiary 左右 slide 时聊天/编辑/删除
- ❔ ChatPage:用户对话输出内容右对齐
-
❔ Chat/Diary:GPT Streaming API
- ❔ ChatPage - 实现 AI 先开口(Alarm/Notification)
- 聊天系统 prompt 的核心是主动性。
- 想做得更真实的话,也可以传递 diary/chat 的 create_date,这样提到“昨天”之类的日期会更自然。
- Alarm 功能实现参考
- 聊天系统 prompt 的核心是主动性。
- ❔ DiaryPage:追加细分情绪关键词 Choice Chips 后设置 DB 联动
- ❔ DiaryPage - AI Comment:把 Choice chips、mood slider 等输入值改成适合
AI Comment System Prompt的形式。- ❔ **之前的
日记或全部对话记录(临时补上的技术债) - ❔ 用户基本信息:姓名、性别、MBTI、……
- ❔ 细分情绪关键词:开心、难过、……
- ❔ 角色设置:Somi、Sena、Minhyuk
- ❔ 情绪数值:1~100 分
- ❔ 回答格式:治愈型(Healing)、建议型(Suggestion)、信息型(Informative)
- ❔ 回答长度:简短、普通、详细
- is New Chat
- 因为该 Chat 的 messages 是第一次创建,所以给第一个创建的 message document 设置
is_initial = true,然后把所有 system prompt 都塞进去。- is_initial = false
- 限制:如果把 system prompt 塞进最开始的 message document,虽然对话中途修改会比较困难,但也不是不可能。 如果存在 is_initial = true 的 document,而且它被修改了,那把那个 document 删除再放回去就行。具体怎么做我还不知道,但总归能想办法。
- 因为该 Chat 的 messages 是第一次创建,所以给第一个创建的 message document 设置
- ❔ **之前的
- ❔ DiaryPage - AI Comment:完成 CRUD
- ❔ 删除 tmp_ai_comment field,改用 doc_ref
- ❔ DiaryPage - AI Comment:角色设置
- ❔ 删除 tmp_ai_comment_by field,改用 doc_ref
- ❔ 用 profile_image field 值输出图片
-
❔ DiaryPage - Drawer - ChatHistoryListTile:order by updated_time
-
❔ DiaryPage - import 后应用 Interactive Slider
-
❔ HomeFeedPage:实现 Search Diary 功能
- ❔ CalendarPage(上方)- Mood Calendar
- ❔ 可以确认按日期写成的帖子数量
- ❔ 点击日期后移动到对应日期
- ❔ CalendarPage(下方)- Mood stats
- ❔ 输出关于当前聚焦月份的统计资料
- ❔ 输出 AI 对所显示统计资料的评论
- ❔ 输出关于当前聚焦月份的统计资料
-
❔ AuthPage:实现 Google Login 功能
- ❔ HomeFeedPage - Bottom Sheet(
+Button):可以在多种选项中选择 1 个- 新日记:Go to DiaryPage
- 情绪记录:Go to MoodPage
- ❔ MoodPage(popup):Mood 设置 slider CRUD
- Create
- Read
- Update
- Delete
- ❔ HomeFeedPage - ListView:显示 FeedCardMood
-
❔ HomeFeedPage - ListView:按时间顺序排序 FeedCardDiary, FeedCardMood, FeedCardChat component(使用 Cloud Function)
- ❔ 到
2025.2.5 周三为止,提取并安装发表用最终版本 APK
💭 日记
本来想趁剩下时间不多再多实现一点功能……结果为了解决问题,把工作时间全用完了。
还是得放下贪心。反正这个项目也不是只为了 AIFFELTHON 短暂开发一下就结束的项目。
✨ GPT-4o 的评论(KPT)
✅ 做得好的地方(Keep)
- 🛠 解决 FleatherViewer 更新问题: 通过使用
didUpdateWidget解决 widget 状态刷新问题,这一点很棒。 - ⚡ HomeFeedPage 过滤优化: 使用 Conditional Visibility 代替 Query filter 来优化性能,这个判断很有效。
- 🔍 在 debugging 过程中的学习: 通过多种尝试亲身体验 Query filter 的限制,并得出 Conditional Visibility 更合适的结论,很有意义。
❌ 可惜的地方(Problem)
- ⏳ 花费时间超出预期: 比起原本计划的功能实现,最终大部分时间都用在了解决问题上,这一点有些可惜。
- 📌 没有提前整理问题解决方式: 之前从导师那里得到的建议没有执行,导致在同一个问题上又花了时间。
🔄 可以尝试的地方(Try)
- 📝 使用问题解决笔记: 记录解决过的问题,养成在遇到类似问题时能快速解决的整理习惯。
- 🚀 调整剩余功能实现策略: 现在时间已经不多了,整理必须实现的功能优先级,把其余部分留到之后改善。
- 🔍 进一步学习 FlutterFlow 内部运行方式: 提高对 FlutterFlow 数据处理方式和 widget 状态管理的理解,提前避免类似问题。
结论
今天花在问题解决上的时间比预想更多,但也因此得到重要学习。剩下期间需要优先实现必须功能,并采用放下不必要贪心的策略。坚持到最后吧!💪🔥
留下评论