Skip to content

fix: remember folder scroll and avoid refetch on back-nav (#104) - #120

Merged
Ericgacoki merged 5 commits into
mainfrom
fix/folder-paging-cache
Jun 10, 2026
Merged

fix: remember folder scroll and avoid refetch on back-nav (#104)#120
Ericgacoki merged 5 commits into
mainfrom
fix/folder-paging-cache

Conversation

@joelkanyi

@joelkanyi joelkanyi commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #104. Supersedes #118.

Scroll position memory

Each folder remembers where you scrolled to. Tapping back returns you to that scroll position at every nesting level.

FoldersViewModel keeps a normalized-path map of (firstVisibleIndex, firstVisibleOffset). The composable saves and restores in a LaunchedEffect keyed on currentFolder.path. Path normalized with trimEnd('/') on both save and lookup because the VM uses different trailing-slash forms between forward and back nav.

The restore effect waits for totalItemsCount > saved.first before calling scrollToItem. A "wait for any item" check failed on cache hits where the first paged item appeared in layoutInfo within a millisecond, making scrollToItem a no-op because the target index didn't exist yet.

Paging cache per folder path

Going back to a folder no longer refetches.

FoldersViewModel keeps a Map<String, Flow<PagingData<FolderContentItem>>> keyed by normalized path. The existing cachedIn(viewModelScope) lives inside the cached Flow, so revisits reuse the cached PagingData without hitting the network. Plain nav events look up the Flow and only build on miss. Pull-to-refresh and OnRetry pass forceRefresh = true which evicts then rebuilds.

OnPullToRefresh is a new explicit event so pull-to-refresh no longer routes through OnClickFolder (which would now be a cache hit).

The updatedTrackFavorites.update { emptyMap() } calls on plain nav events were dropped. Those wipes caused the combine operator to re-emit a fresh PagingData through the cached pipeline (flickering the list) and were also clearing user-toggled favorites in this session. Only forceRefresh wipes favorites now.

A small screen-side guard remembers paths that have ever shown items so the empty-state spinner doesn't briefly flash on cache-hit re-subscription.

Tested on Pixel 7 Pro

  • Deep scroll, into subfolder, back: position restored
  • Multi-level (A → A/B → A/B/C, back-back-back): each level restores
  • Breadcrumb up the tree: instant, no fetch
  • Tab switch and return: state survives
  • Pull-to-refresh: evicts and refetches

Keep a per-path map of (firstVisibleIndex, firstVisibleOffset) in FoldersViewModel.
When the LazyColumn observes a folder change, wait for items to load then restore
the saved position if any. Subsequent scroll changes save back to the map.

Path is normalized with trimEnd('/') on both save and lookup. The VM already
normalizes when matching breadcrumb entries, but the raw Folder.path varies
(some paths carry a trailing slash, others don't), which would otherwise key
two distinct entries for the same folder and break restore on back-nav.
Each folder navigation event was rebuilding the Flow<PagingData> from
scratch, throwing away the previous cachedIn cache. Result: every back-nav
or breadcrumb hop re-fetched from the server even for folders we just
visited.

Keep a map of path -> Flow keyed by the normalized path. Plain nav events
(OnClickFolder, OnClickNavPath, OnBackNav) look the Flow up, build only on
miss, and reuse it on revisits. The cachedIn'd PagingData survives so the
LazyPagingItems replays it without a network call.

Pull-to-refresh and OnRetry pass forceRefresh=true, which evicts the entry
for that path and rebuilds. OnPullToRefresh is a new explicit event so
pull-to-refresh doesn't go through OnClickFolder anymore (which would now
be a cache hit).

Also stop wiping updatedTrackFavorites on every plain nav event. The
favorites override map is intended to persist session-toggled favorites,
and clearing it on every back-nav was both incorrect and caused a brief
combine re-emission flicker on the cached path. Only force-refresh paths
wipe the favorites map.

A small screen-side guard remembers paths that have ever shown items so
the empty-state spinner doesn't briefly flash during the LazyPagingItems
re-subscription on cache hits.
# Conflicts:
#	feature/folder/src/main/java/com/android/swingmusic/folder/presentation/viewmodel/FoldersViewModel.kt
The scroll restore filter was waiting for totalItemsCount > 0, but on a
cache hit the very first item appears in layoutInfo within a millisecond.
scrollToItem(savedIndex, savedOffset) would fire while only 1 item was
laid out, becoming a no-op. By the time the rest of the cached items
populated, restoration had already given up.

Wait for totalItemsCount > saved.first so the target index actually
exists when scrollToItem runs.
@Ericgacoki
Ericgacoki merged commit bbd3aae into main Jun 10, 2026
1 check passed
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.

[Feature request] Remember position of last opened tab

2 participants