fix: remember folder scroll and avoid refetch on back-nav (#104) - #120
Merged
Conversation
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
approved these changes
Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
FoldersViewModelkeeps a normalized-path map of(firstVisibleIndex, firstVisibleOffset). The composable saves and restores in aLaunchedEffectkeyed oncurrentFolder.path. Path normalized withtrimEnd('/')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.firstbefore callingscrollToItem. A "wait for any item" check failed on cache hits where the first paged item appeared inlayoutInfowithin a millisecond, makingscrollToItema no-op because the target index didn't exist yet.Paging cache per folder path
Going back to a folder no longer refetches.
FoldersViewModelkeeps aMap<String, Flow<PagingData<FolderContentItem>>>keyed by normalized path. The existingcachedIn(viewModelScope)lives inside the cached Flow, so revisits reuse the cachedPagingDatawithout hitting the network. Plain nav events look up the Flow and only build on miss. Pull-to-refresh andOnRetrypassforceRefresh = truewhich evicts then rebuilds.OnPullToRefreshis a new explicit event so pull-to-refresh no longer routes throughOnClickFolder(which would now be a cache hit).The
updatedTrackFavorites.update { emptyMap() }calls on plain nav events were dropped. Those wipes caused thecombineoperator to re-emit a freshPagingDatathrough the cached pipeline (flickering the list) and were also clearing user-toggled favorites in this session. OnlyforceRefreshwipes 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