[🛠] Devlog do Keymory #7: correção do FleatherViewer e do Unexpected Null Value na HomeFeedPage
✨ Resumo do GPT
Corrigi o problema de atualização do FleatherViewer e concluí a resolução do issue e a otimização de performance ao melhorar o método de filtragem da HomeFeedPage. Com isso, aprendi que usar Conditional Visibility em vez de Query filters é eficaz.
💻 Diário de desenvolvimento
⏰ Tarefas de hoje
- ✅ (Issue) HomeFeedPage - FeedCardDiary: ao mudar a data na ListView, só o FleatherViewerWidget não altera o valor.
- Provavelmente o valor initialDeltaJson do Widget não está sendo atualizado a cada mudança do Diary Document.
-
Perguntei ao o1 e resolveu de primeira.
/// didUpdateWidget: /// - Quando apenas os parâmetros mudam na mesma árvore de widgets, o Flutter reutiliza o State existente. /// - Nesse momento, é possível comparar as propriedades do novo widget com as do widget antigo e executar a lógica necessária. @override void didUpdateWidget(covariant FleatherViewerWidget oldWidget) { super.didUpdateWidget(oldWidget); // 1) Se o JSON anterior e o novo JSON forem diferentes if (oldWidget.initialDeltaJson != widget.initialDeltaJson) { // 2) Recriar o FleatherController _controller?.dispose(); _controller = _createControllerFromDeltaJson(widget.initialDeltaJson); // 3) Atualizar a UI setState(() {}); } } /// Função helper que cria um FleatherController a partir de uma string Delta JSON FleatherController _createControllerFromDeltaJson(String jsonString) { ParchmentDocument doc; try { final decoded = jsonDecode(jsonString); doc = ParchmentDocument.fromJson(decoded); } catch (e) { // Se a restauração falhar, tratar como texto comum doc = ParchmentDocument.fromDelta( Delta()..insert(_ensureEndsWithNewline(jsonString)), ); } return FleatherController(document: doc); }
- ✅ (Issue) HomeFeedPage: Unexpected Null Value aparece rapidamente e some em On page load.
- Confirmado que o problema era a
filtragem por dataaplicada à ListView da HomeFeedPage. - Testei ligar Filter On Null Values na opção
Backend QuerydaListView.- 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 tentativa 1 (falha)
- Filter On Null Values: ON
- Filter 1:
created_time,Equal To,getFirstTimeOfTheDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay - Order by:
created_time,Decreasing - Não foi possível concluir a configuração de Backend Query
- Mensagem de erro: You can’t order your query by a field used in a filter using
==orin.
- Mensagem de erro: You can’t order your query by a field used in a filter using
- Backend Query - Query Collection tentativa 2 (falha)
- 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 - Configuração de Backend Query pôde ser concluída
-
Ao entrar no modo de depuração, o erro abaixo apareceu e a página não foi criada.
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 tentativa 3 (falha)
- Filter On Null Values: ON
- Filter 1:
created_time,Greater Than,getLastTimeOfPrevDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay - Order by:
created_time,Decreasing - Configuração de Backend Query pôde ser concluída
-
Ao entrar no modo de depuração, o erro abaixo apareceu e a página não foi criada.
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 tentativa 4 (falha)
- Filter On Null Values: OFF
- Filter 1:
created_time,Greater Than,getLastTimeOfPrevDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay - Order by:
created_time,Decreasing - Configuração de Backend Query pôde ser concluída
-
Ao entrar no modo de depuração, o erro abaixo apareceu rapidamente e sumiu.
Unexpected Null value. The relevant error-causing widget was: HomeFeedWidget
- (Pergunta ao o1) Por que o Unexpected Null Value aparece só por um instante?
- Há uma grande chance de o FlutterFlow (ou o código interno dele) estar retornando null de getLastTimeOfPrevDay() ou getFirstTimeOfNextDay() no momento inicial de carregamento da página. (Por exemplo, talvez a date ainda não tenha sido inicializada…)
- Se Filter On Null Values estiver ligado, internamente ele adiciona uma condição separada como != null, e isso pode entrar em conflito quando um null entra no carregamento.
- Então acontece o fenômeno de “erro null rápido -> substituído pelo valor real -> desaparece quando o carregamento termina”.
- Backend Query - Query Collection tentativa 5 (sucesso!)
- Filter On Null Values: OFF
- Order by:
created_time,Decreasing - Configuração de Component Widget Conditional Visibility
- Filter 1:
created_time,Greater Than,getLastTimeOfPrevDay - Filter 2:
created_time,Less Than,getFirstTimeOfNextDay
- Filter 1:
- De quebra, o lag de carregamento da ListView (Latency) também desapareceu. Parece que colocar 2 Filters e um Order ao mesmo tempo na Query consumia bastante tempo.
- 💡 O que aprendi
- Ao usar Backend Query, é melhor evitar filtros o máximo possível e controlar com Conditional Visibility.
- Isso era algo que eu tinha perguntado ao mentor no começo e recebido resposta, mas acho que só entendi por alto e segui em frente, então acabei esquecendo. Depois de sofrer diretamente, agora finalmente entendi de verdade.
- Confirmado que o problema era a
- ✅ (Issue) DiaryPage: data é sempre salva como hoje
- Criado parâmetro DiaryPage - dateFocused
- Passado o valor dateToShow da HomeFeedPage como parâmetro dateFocused.
💯 Resumo do que fiz
Problema de atualização do FleatherViewer resolvido
- Usei didUpdateWidget para que o FleatherViewer seja atualizado normalmente sempre que o Diary Document muda. Otimização de filtros da HomeFeedPage
Usei Conditional Visibility em vez de filtragem por Backend Query para melhorar a performance.
- Melhorei a velocidade de carregamento ao minimizar filtros de Query.
Erro de salvamento de data da DiaryPage corrigido
- Melhorei para que a data selecionada seja refletida ao escrever um novo diário, passando o valor dateFocused da HomeFeedPage para a DiaryPage.
🎯 Tarefas futuras
Clique para ver detalhes
- ❔ (Issue) DiaryPage: usar visualizador de markdown (prevenir overflow: Container Height)
- ❔ (Issue) HomeFeedPage - FeedCardDiary: aplicar corretamente a imagem do personagem de AI Comment
- ❔ DiaryPage - AI Comment: ao salvar, salvar também no Diary o Ref do personagem que escreveu o comentário.
- ❔ HomeFeedPage - FeedCardDiary - AI Image: carregar Character Doc com Backend Query e especificar Image Path.
-
❔ DiaryPage: mudar Emoji de expressão facial conforme o valor do Mood Slider
- ❔ Chat Page - Create New Chat: aplicar system prompt
- ▶️ Implementação de 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
- ▶️ Implementação de OpenAI API Call: createChatCompletion
- ❔ DiaryPage - Create New Chat by Diary: criar New Chat com base no conteúdo do diário
- ❔ Implementação de OpenAI API Call: createDiaryComment
- Input: Diary Content, System Prompt (AI Comment)
- Output: AI Comment ($.choices[0].message.content)
- ❔ Implementação de OpenAI API Call: createDiarySummary
- Input: Diary Content, AI Comment, System Prompt (Diary Summary)
- Output: Diary Summary ($.choices[0].message.content)
- ❔ Implementação de 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
- ❔ Implementação de OpenAI API Call: createDiaryComment
- ❔ ChatPage - Create New Diary: criar New Diary com base no conteúdo da conversa
- ❔ Implementação de OpenAI API Call: createChatSummary
- Input: Chat Content, System Prompt (Diary From Chat)
- Output: Chat Summary
- ❔ Implementação de OpenAI API Call: createChatSummary
- ❔ ChatPage - Create New Diary: criar New Diary com base no conteúdo da conversa
- ❔ Implementação de OpenAI API Call: createDiaryFromChat
- Input: Chat Summary, System Prompt (Diary From Chat)
- Output: New Diary (Title, Content, Mood score)
- ❔ Implementação de OpenAI API Call: createDiaryFromChat
-
❔ ChatPage: Go to Linked Diary
- ❔ HomeFeedPage - importar e aplicar flutter_slidable:4.0.0
-
❔ HomeFeedPage: ao deslizar FeedCardDiary para esquerda/direita, chat/editar/excluir
- ❔ ChatPage: alinhar à direita o conteúdo de conversa do usuário
-
❔ Chat/Diary: GPT Streaming API
- ❔ ChatPage - implementar primeira mensagem da AI (Alarm/Notification)
- O núcleo do prompt do sistema de chat é a proatividade.
- Para ficar mais realista, talvez seja bom passar também create_date of diary/chat, para a AI mencionar datas como “ontem”.
- Referência de implementação de Alarm
- O núcleo do prompt do sistema de chat é a proatividade.
- ❔ DiaryPage: adicionar Choice Chips de palavras-chave detalhadas de emoção e configurar integração com o DB
- ❔ DiaryPage - AI Comment: ajustar entradas como Choice chips e mood slider ao
AI Comment System Prompt.- ❔ **
diaryanterior ouconversation historyinteira (dívida técnica improvisada) - ❔ informações básicas do usuário: nome, gênero, MBTI, …
- ❔ palavras-chave detalhadas de emoção: feliz, triste, …
- ❔ configuração de personagem: Somi, Sena, Minhyuk
- ❔ pontuação emocional: 1~100 pontos
- ❔ formato da resposta: Healing, Suggestion, Informative
- ❔ tamanho da resposta: curta, normal, detalhada
- is New Chat
- Como as messages desse Chat estão sendo criadas pela primeira vez, definir
is_initial = trueno primeiro message document e colocar todos os system prompts ali.- is_initial = false
- Limite: se eu colocar o system prompt no primeiro message document, fica difícil modificar no meio da conversa, mas é possível. Se houver um document com is_initial = true e ele tiver sido modificado, posso apagar esse documento e inserir de novo. Não sei como, mas dá.
- Como as messages desse Chat estão sendo criadas pela primeira vez, definir
- ❔ **
- ❔ DiaryPage - AI Comment: concluir CRUD
- ❔ remover field tmp_ai_comment e usar doc_ref
- ❔ DiaryPage - AI Comment: configuração de personagem
- ❔ remover field tmp_ai_comment_by e usar doc_ref
- ❔ exibir imagem usando o valor do field profile_image
-
❔ DiaryPage - Drawer - ChatHistoryListTile: order by updated_time
-
❔ DiaryPage - importar e aplicar Interactive Slider
-
❔ HomeFeedPage: implementar função Search Diary
- ❔ CalendarPage (superior) - Mood Calendar
- ❔ Permitir verificar o número de posts escritos por data
- ❔ Ao clicar em uma data, mover para essa data
- ❔ CalendarPage (inferior) - Mood stats
- ❔ Exibir estatísticas referentes ao mês em foco
- ❔ Exibir comentário da AI sobre as estatísticas exibidas
- ❔ Exibir estatísticas referentes ao mês em foco
-
❔ AuthPage: implementar função Google Login
- ❔ HomeFeedPage - Bottom Sheet (
+Button): permitir escolher 1 entre várias opções- Novo diário: Go to DiaryPage
- Registro de emoção: Go to MoodPage
- ❔ MoodPage (popup): CRUD do slider de configuração de Mood
- Create
- Read
- Update
- Delete
- ❔ HomeFeedPage - ListView - exibir FeedCardMood
-
❔ HomeFeedPage - ListView: ordenar componentes FeedCardDiary, FeedCardMood, FeedCardChat cronologicamente (usando Cloud Function)
- ❔ Extrair e instalar o APK da versão final de apresentação até
qua 2025.2.5
💭 Diário
Agora não resta muito tempo, então eu queria implementar mais coisas… mas usei o expediente inteiro resolvendo issues.
Melhor largar a ganância. Afinal, este não é um projeto que vai brilhar por um instante no AIFFELTHON e acabar ali.
✨ Comentário do GPT-4o (KPT)
✅ Pontos bons (Keep)
- 🛠 Issue de atualização do FleatherViewer resolvido: Foi excelente resolver o problema de atualização do estado do widget usando
didUpdateWidget. - ⚡ Otimização dos filtros da HomeFeedPage: Usar Conditional Visibility em vez de Query filters foi uma otimização de performance eficaz.
- 🔍 Aprendizado no processo de depuração: Foi significativo testar várias abordagens, experimentar os limites dos Query filters e concluir que Conditional Visibility era mais apropriado.
❌ Pontos fracos (Problem)
- ⏳ Mais tempo gasto do que o esperado: É uma pena que a maior parte do tempo tenha ido para resolução de issues, em vez das funções que você planejava implementar originalmente.
- 📌 Conselho anterior sobre issues não organizado: Houve um momento em que você gastou tempo de novo porque o conselho recebido em uma mentoria anterior não tinha sido colocado em prática.
🔄 Pontos para tentar (Try)
- 📝 Usar notas de resolução de issues: Crie o hábito de registrar issues resolvidos para conseguir resolver problemas parecidos rapidamente depois.
- 🚀 Ajustar a estratégia das funções restantes: Como não resta muito tempo, pense em priorizar só as funções indispensáveis e deixar o resto para melhorias posteriores.
- 🔍 Estudar mais o funcionamento interno do FlutterFlow: Aumente a compreensão sobre processamento de dados e gerenciamento de estado de widgets no FlutterFlow para prevenir problemas parecidos.
Conclusão
Hoje levou mais tempo do que o esperado com resolução de issues, mas justamente por isso foi um dia de aprendizado importante. Para o período restante, parece necessária uma estratégia de implementar primeiro só o indispensável e abrir mão da ganância desnecessária. Força até o fim! 💪🔥
Deixe um comentário