From d446839fa51aebf779b528aab39440bb6fe37280 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 14:21:04 +0000 Subject: [PATCH] fix(geocaches): observe the loaded feed's flow instead of reading .value FeedState.Loaded is handed out once and then mutated through its inner MutableStateFlow, so reading .value inside composition froze both screens at whatever the feed held when they first composed: - the map tab never grew a pin for a cache that streamed in afterwards - a hunt's progress bar never moved when a new find was logged Both now collect that inner flow, matching GalleryFeedLoaded. Caught by Android Lint (StateFlowValueCalledInComposition), which the repo's pre-commit hook does not run; verified with :amethyst:lintFdroidBenchmark, which goes from 2 errors to 0. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WG7T9hAHDMKPaHsWvqySEr --- .../loggedIn/geocaches/GeocacheFinds.kt | 25 ++++++++++++------ .../loggedIn/geocaches/map/GeocacheMapTab.kt | 26 ++++++++++++++----- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/GeocacheFinds.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/GeocacheFinds.kt index 3aaf4799eb..f6cdc5e142 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/GeocacheFinds.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/GeocacheFinds.kt @@ -39,13 +39,22 @@ fun rememberMyFoundCacheIds(accountViewModel: AccountViewModel): Set { val finds by accountViewModel.feedStates.geocacheFindsFeed.feedContent .collectAsStateWithLifecycle() - val notes = - when (val state = finds) { - is FeedState.Loaded -> state.feed.value.list - else -> emptyList() - } - - return remember(notes) { - notes.mapNotNullTo(mutableSetOf()) { (it.event as? GeocacheFoundLogEvent)?.geocacheId() } + return when (val state = finds) { + is FeedState.Loaded -> rememberFoundCacheIds(state) + else -> emptySet() + } +} + +/** + * Collects the loaded feed's own flow. [FeedState.Loaded] is handed out once and then mutated + * through its inner flow, so reading `.value` here would freeze the set at whatever the feed held + * when the hunt screen first composed — the progress bar would never move. + */ +@Composable +private fun rememberFoundCacheIds(state: FeedState.Loaded): Set { + val loaded by state.feed.collectAsStateWithLifecycle() + + return remember(loaded) { + loaded.list.mapNotNullTo(mutableSetOf()) { (it.event as? GeocacheFoundLogEvent)?.geocacheId() } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/map/GeocacheMapTab.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/map/GeocacheMapTab.kt index 43333f6a0c..91fc908fd7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/map/GeocacheMapTab.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/map/GeocacheMapTab.kt @@ -111,13 +111,7 @@ fun GeocacheMapTab( val feedState by feedContentState.feedContent.collectAsStateWithLifecycle() val found = rememberMyFoundCacheIds(accountViewModel) - val listings = - when (val state = feedState) { - is FeedState.Loaded -> - state.feed.value.list - .mapNotNull { it.event as? GeocacheListingEvent } - else -> emptyList() - } + val listings = rememberGeocacheListings(feedState) var peek by remember { mutableStateOf(null) } @@ -368,3 +362,21 @@ private fun markerFor( } } } + +/** + * Collects the loaded feed's own flow. [FeedState.Loaded] is handed out once and then mutated + * through its inner flow, so reading `.value` here would pin the map to whatever had arrived when + * the tab first composed — caches streaming in from relays would never get a pin. + */ +@Composable +private fun rememberGeocacheListings(feedState: FeedState): List = + when (feedState) { + is FeedState.Loaded -> { + val loaded by feedState.feed.collectAsStateWithLifecycle() + + remember(loaded) { + loaded.list.mapNotNull { it.event as? GeocacheListingEvent } + } + } + else -> emptyList() + }