perf(mailbox): reader→inbox return flashes "no messages" and reloads (empty-state gate + cold LazyPagingItems) #219

Closed
opened 2026-07-03 15:21:40 +00:00 by JMR-dev · 1 comment
JMR-dev commented 2026-07-03 15:21:40 +00:00 (Migrated from github.com)

Source: perf analysis of reader → inbox back-navigation (user-reported: multiple-second delay + a brief "No messages" flash on return).

The ViewModel and its flows ARE retained across the round trip; the UI-side LazyPagingItems is recreated cold (itemCount == 0) on return. Two distinct root causes:

  1. The flash — the unified list's empty-state gate at MailboxScreen.kt:297-305 suppresses "No messages" only while refresh is LoadState.Loading, not during the itemCount == 0 && refresh == NotLoading re-presentation window (Paging pushes the item snapshot and load-state on separate collectors), so the empty state renders for a frame.
  2. The multi-second latency — opening an unread message writes setRead (MailRepositoryImpl.kt:135), invalidating the Room PagingSource; Paging cold-loads a new generation on return (the cache fills lazily and the mailbox has no collector while reading), amplified by concurrent sync/prefetch writes each re-invalidating the source.

Fixes (both):

  • Gate the empty state on itemCount == 0 && refresh is LoadState.NotLoading && append.endOfPaginationReached.
  • Keep a live downstream collector on the cached paged flow across the reader visit so the post-setRead generation loads in the background and the return replays a full window with zero empty frames.

Sequencing: implement AFTER #214 (paged folder/search) lands — both rewrite the same MailboxScreen/MailboxViewModel paging code, and the empty-state gate + keep-alive should then apply uniformly to all paged lists.

**Source:** perf analysis of reader → inbox back-navigation (user-reported: multiple-second delay + a brief "No messages" flash on return). The ViewModel and its flows ARE retained across the round trip; the UI-side `LazyPagingItems` is recreated cold (`itemCount == 0`) on return. Two distinct root causes: 1. **The flash** — the unified list's empty-state gate at `MailboxScreen.kt:297-305` suppresses "No messages" only while `refresh is LoadState.Loading`, not during the `itemCount == 0 && refresh == NotLoading` re-presentation window (Paging pushes the item snapshot and load-state on separate collectors), so the empty state renders for a frame. 2. **The multi-second latency** — opening an unread message writes `setRead` (`MailRepositoryImpl.kt:135`), invalidating the Room `PagingSource`; Paging cold-loads a new generation *on return* (the cache fills lazily and the mailbox has no collector while reading), amplified by concurrent sync/prefetch writes each re-invalidating the source. **Fixes (both):** - Gate the empty state on `itemCount == 0 && refresh is LoadState.NotLoading && append.endOfPaginationReached`. - Keep a live downstream collector on the cached paged flow across the reader visit so the post-`setRead` generation loads in the background and the return replays a full window with zero empty frames. **Sequencing:** implement AFTER #214 (paged folder/search) lands — both rewrite the same `MailboxScreen`/`MailboxViewModel` paging code, and the empty-state gate + keep-alive should then apply uniformly to all paged lists.
JMR-dev commented 2026-07-03 15:37:30 +00:00 (Migrated from github.com)

PR #223 (issue #214) rewrote the mailbox list into a single paged render path and, in doing so, replaced the empty-state gate with the correct itemCount == 0 && refresh is LoadState.NotLoading && append.endOfPaginationReached condition — i.e. it subsumes fix #1 (the empty-state flash) from this ticket.

Remaining scope for #219: the structural latency fix — keep a live downstream collector on the cached paged flow across the reader visit so the post-setRead generation loads in the background (no cold reload / blank window on return). To be implemented on top of #223.

PR #223 (issue #214) rewrote the mailbox list into a single paged render path and, in doing so, replaced the empty-state gate with the correct `itemCount == 0 && refresh is LoadState.NotLoading && append.endOfPaginationReached` condition — i.e. it **subsumes fix #1 (the empty-state flash)** from this ticket. Remaining scope for #219: the **structural latency fix** — keep a live downstream collector on the cached paged flow across the reader visit so the post-`setRead` generation loads in the background (no cold reload / blank window on return). To be implemented on top of #223.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#219