Skip to content

Fix recent-emoji list leaking into UI on unrelated parent rebuild - #269

Merged
Fintasys merged 1 commit into
masterfrom
fix-recent-emoji-state-leak
Jul 22, 2026
Merged

Fix recent-emoji list leaking into UI on unrelated parent rebuild#269
Fintasys merged 1 commit into
masterfrom
fix-recent-emoji-state-leak

Conversation

@Fintasys

Copy link
Copy Markdown
Owner

Problem

Reported in #238: selecting an emoji while the RECENT tab is visible could make the recent list suddenly re-order/jump later, in reaction to an unrelated rebuild triggered by a parent widget.

Root cause

EmojiPickerState.updateRecentEmoji(refresh: false) only gated the setState() call with the refresh flag — it always mutated _categoryEmoji regardless:

_recentEmoji = recentEmoji;
_categoryEmoji[recentTabIndex] = _categoryEmoji[recentTabIndex].copyWith(
  emoji: _recentEmoji.map((e) => e.emoji).toList(),
);
if (mounted && refresh) {
  setState(() {});
}

_categoryEmoji is the same list instance held by reference in EmojiViewState (_state = EmojiViewState(_categoryEmoji, ...)), and DefaultEmojiPickerView rebuilds the recent grid straight from it on every build(). So on the RECENT tab, tapping an emoji reorders the data silently (no setState, no visible change) — but the next stray rebuild from a parent widget paints that already-mutated data, and the recent list jumps.

This is exactly the inconsistency described in the #238 discussion: "it updates the list in the state, but doesn't update the UI".

Fix

Skip the displayed-category mutation entirely when refresh is false. The persisted store is already updated by the caller (addEmojiToRecentlyUsed / addEmojiToPopularUsed write before this call), so nothing is lost — the change simply becomes visible on the next explicit refresh instead of leaking in early.

Test

Adds test/recent_emoji_state_test.dart:

  • refresh:false must not leak — persists a recent emoji via the public EmojiPickerUtils.addEmojiToRecentlyUsed API (which calls updateRecentEmoji(refresh: false)), then forces an unrelated parent rebuild and asserts the recent tab is unchanged. Fails on the old code, passes with the fix.
  • refresh:true updates immediately — asserts an explicit refresh still renders the new recent emoji right away.

Full suite (15 tests) passes; flutter analyze clean.

🤖 Generated with Claude Code

`updateRecentEmoji(refresh: false)` still mutated `_categoryEmoji`, the
list held by reference in `EmojiViewState`. Only the `setState()` call
was gated by `refresh`, so the "don't show yet" data surfaced on the
next unrelated rebuild triggered by a parent widget — the RECENT tab
would re-order under the user (originally reported in #238).

Skip the displayed-category mutation entirely when `refresh` is false.
The persisted store is already updated by the caller, so the change is
not lost — it becomes visible on the next explicit refresh instead of
leaking early.

Adds a regression test covering both the refresh:false (no leak on
parent rebuild) and refresh:true (immediate update) paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Fintasys
Fintasys force-pushed the fix-recent-emoji-state-leak branch from a93e1c0 to 70e5e21 Compare July 22, 2026 03:38
@Fintasys
Fintasys marked this pull request as ready for review July 22, 2026 03:49
@Fintasys
Fintasys merged commit a5a1a38 into master Jul 22, 2026
1 check passed
@Fintasys
Fintasys deleted the fix-recent-emoji-state-leak branch July 22, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant