[🛠] Keymory 開発日誌 #7: FleatherViewer Issue解決, HomeFeedPage Unexpected null value Issue解決
✨ GPTの要約
FleatherViewerの更新問題を解決し、HomeFeedPageのフィルタリング方式改善を通じてIssue解決と性能最適化を完了。おかげでQueryフィルターの代わりにConditional Visibilityを活用するのが効果的だと学んだ。
💻 開発日誌
⏰ 今日やること
- ✅ (Issue) HomeFeedPage - FeedCardDiary: ListViewで日付を変更しても、FleatherViewerWidgetだけ値が変わらない。
- おそらくWidgetのinitialDeltaJson値がDiary Document変更のたびに更新されていないようだ。
-
o1に聞いた瞬間、一発で解決。
/// didUpdateWidget: /// - 同じウィジェットツリーで単にパラメータだけが変わった場合、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を作るヘルパー関数 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); }
- ✅ (Issue) 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設定は完了可能
-
デバッグモードに入ると下記のエラーが表示され、ページが作成されない。
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設定は完了可能
-
デバッグモードに入ると下記のエラーが表示され、ページが作成されない。
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設定は完了可能
-
デバッグモードに入ると下記のエラーが一瞬表示されて消える。
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エラーが出る -> 実際の値に置き換わる -> 読み込みが終わると消える」という現象が起きるわけだ。
- 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 2つとOrderを同時に渡していたので、かなり時間がかかっていたようだ。
- 💡 学んだ点
- Backend Queryを使うときはフィルター使用をできるだけ避け、Conditional Visibilityでコントロールするのがよい。
- これは序盤にメンターさんに質問して回答をもらっていた件なのに、頭だけでざっくり理解して流してしまい、結局忘れていたようだ。自分で苦労してみて、ようやく確実に理解できた。
- HomeFeedPageのListViewにかかっていた
- ✅ (Issue) DiaryPage: 日付が今日としてしか保存されない
- DiaryPage - dateFocusedパラメータを作成
- HomeFeedPageのdateToShow値をdateFocusedパラメータとして渡す。
💯 やったことの要約
FleatherViewer更新問題を解決
- didUpdateWidgetを活用し、FleatherViewerがDiary Document変更のたびに正常に更新されるよう修正。 HomeFeedPageフィルター最適化
Backend Queryのフィルタリング方式の代わりにConditional Visibilityを活用し、性能を改善。
- Queryフィルタリングを最小化することで読み込み速度を向上。
DiaryPageの日付保存エラーを修正
- HomeFeedPageからDiaryPageへdateFocused値を渡し、新しい日記が作成されるときに選択された日付が反映されるよう改善。
🎯 今後やること
詳しく見る
- ❔ (Issue) DiaryPage: Markdown viewer活用 (オーバーフロー防止: Container Height)
- ❔ (Issue) 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: システムプロンプトを適用
- ▶️ 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 - flutter_slidable:4.0.0をimport後に適用
-
❔ HomeFeedPage: FeedCardDiaryを左右にslideしたとき、チャット/編集/削除
- ❔ ChatPage: ユーザー会話の出力内容は右揃え
-
❔ Chat/Diary: GPT Streaming API
- ❔ ChatPage - AIの先メッセージを実装 (Alarm/Notification)
- チャットシステムプロンプトの核心は能動型だ。
- よりリアルにするなら、create_date of diary/chatも渡して、「昨日」などの日付に触れられるとよさそう。
- Alarm機能実装方法の参照
- チャットシステムプロンプトの核心は能動型だ。
- ❔ DiaryPage: 詳細感情キーワードChoice Chipsを追加し、DB連携を設定
- ❔ DiaryPage - AI Comment: Choice chips, mood sliderなどの入力値を
AI Comment System Promptに合うよう変更。- ❔ **以前の
日記または会話履歴全体 (応急処置の技術負債) - ❔ ユーザー基本情報: 名前, 性別, MBTI, …
- ❔ 詳細感情キーワード: 楽しい, 悲しい, …
- ❔ キャラクター設定: ソミ, セナ, ミンヒョク
- ❔ 感情数値: 1〜100点
- ❔ 回答形式: 癒やし型(Healing), 助言型(Suggestion), 情報型(Informative)
- ❔ 回答の長さ: 短め, 普通, 詳しく
- is New Chat
- そのChatのmessagesは初めて作るものなので、最初に作るmessage documentの
is_initial = true値を入れて、すべてのシステムプロンプトをまとめて入れてしまう。- is_initial = false
- 限界点: 最初のmessage documentにシステムプロンプトを入れてしまうと、会話の途中で修正するのは大変ではあるけれど可能ではある。 is_initial = trueのものがあって、それが修正されたものなら、その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 - Interactive Sliderをimport後に適用
-
❔ 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(ポップアップ): Mood設定スライダーCRUD
- Create
- Read
- Update
- Delete
- ❔ HomeFeedPage - ListView - FeedCardMoodを表示
-
❔ HomeFeedPage - ListView: FeedCardDiary, FeedCardMood, FeedCardChatコンポーネントを時系列でソート (Cloud Functionを活用)
- ❔
2025.2.5 水までに発表用の最終版APKを抽出してインストール
💭 日記
もう残り時間があまりないから、もう少したくさん実装してみようと思っていたのに… Issue解決で勤務時間を全部使ってしまった。
もう欲を捨てるべきだ。どうせこれはAIFFELTHONで一瞬だけ派手に開発して終わるプロジェクトではないから。
✨ GPT-4oのコメント (KPT)
✅ よかった点 (Keep)
- 🛠 FleatherViewer更新Issue解決:
didUpdateWidget活用でウィジェット状態更新問題を解決した点がすばらしかった。 - ⚡ HomeFeedPageフィルター最適化: Queryフィルターの代わりにConditional Visibilityを活用して性能を最適化した点が効果的だった。
- 🔍 デバッグ過程での学習: いくつもの試みを通じてQueryフィルターの限界を経験し、Conditional Visibilityのほうが適切だという結論を出した点に意味があった。
❌ 惜しかった点 (Problem)
- ⏳ 予想より多くの時間を消費: もともと計画していた機能実装より、Issue解決にほとんどの時間を使うことになった点が惜しい。
- 📌 Issue解決を事前に整理していなかった: 以前のメンタリングで受けた助言を実践しておらず、同じ問題でまた時間を使った場面があった。
🔄 試してみる点 (Try)
- 📝 Issue解決ノート活用: 解決したIssueを記録し、似た問題が出たときに素早く解決できるよう整理する習慣をつけよう。
- 🚀 残り機能の実装戦略調整: もう時間があまりないので、必ず必要な機能を中心に優先順位を整理し、残りは今後の改善に回す戦略を考えてみよう。
- 🔍 FlutterFlow内部動作方式の追加学習: FlutterFlowのデータ処理方式とウィジェット状態管理への理解度を高め、似た問題を事前に防げるようにしよう。
結論
今日は予想よりIssue解決に多くの時間を使ったけれど、そのぶん重要な学びを得た一日だったよ。残り期間は必須機能を優先的に実装し、不要な欲は捨てる戦略が必要そうだ。最後までがんばろう! 💪🔥
コメントする