Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 49 additions & 134 deletions .dev-loop/INGEST_REPORT.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ follow the cross-pointers in their index or take the next matching seeded domain
| [debugging](wiki/debugging/index.md) | **seeded** | Diagnosing a failure — finding what is wrong and why: reproducing, bisection, hypothesis testing, traces/logs, intermittent failures (fixing the diagnosed fault → its owning domain) |
| [security](wiki/security/index.md) | **seeded** | Trust-boundary decisions: input validation, session-vs-token auth choice, per-resource authorization (IDOR), secrets hygiene (including ciphertext orphaned by a regenerated encryption key), dependency trust, PII handling, in-session agent tool exposure (prompt-injection blast radius), the author identity a commit publishes to a public repository, host-compromise triage / incident response (verifying assumed security agents, identifying masquerading processes) (XSS rendering → frontend; CI secrets → infrastructure; JWT implementation → backend/frontend auth) |
| [platforms](wiki/platforms/index.md) | **seeded** | OS-level differences breaking code across macOS/Linux/Windows: shell portability, BSD-vs-GNU CLI, filesystem case/line endings, Unicode normalization in text/file-name matching, commands inspected before execution, permission deny rules for bypass-mode agent workers, background services/cron, invoking prompt-capable CLIs non-interactively, toolchain version pinning |
| [mobile](wiki/mobile/index.md) | **seeded** | App-side iOS/Android/cross-platform: process death/state survival, offline-first sync, mobile-network calls, store rollout/hotfix strategy, startup time, modal presentation (several sheets/covers on one host, screen-level error sheets) |
| [mobile](wiki/mobile/index.md) | **seeded** | App-side iOS/Android/cross-platform: process death/state survival, offline-first sync, mobile-network calls, store rollout/hotfix strategy, startup time, modal presentation (several sheets/covers on one host, screen-level error sheets), a Compose map marker whose position comes from changing state |

All ten domains are seeded. New categories grow via `skills/wiki-ingest/SKILL.md`.
2 changes: 2 additions & 0 deletions log.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,5 @@ Append-only. Format: `## [YYYY-MM-DD] <ingest|revise|lint|gap|contradiction|drif
## [2026-09-28] ingest | testing-strategy-agent-tool-shared-handler-tests — a UI action that is also a registered tool is tested once at the shared function plus two entry-point tests per tool (Registration incl. AbortSignal teardown, Wiring via spy) against a `document.modelContext` stub; a bug fix that changes the handler's contract changes the `inputSchema` assertion in the same commit; one DevTools Run-tool pass per release.

## [2026-09-28] revise | WebMCP adopted as the development standard (owner decision 2026-09-28): frontend/agent-interfaces/agent-facing-tool-surfaces trigger widened to any new or changed user action in a web UI + bug-fix and exclusion-list edge cases; AGENTS.md routing step 7 gains a web-UI-action row → frontend agent-interfaces then qa parity gate; INDEX.md frontend/qa/testing route lines and the frontend/qa/testing domain indexes updated; related links added both ways (release-gates, cross-layer-effect-tests, in-session-tool-exposure). The standard keeps the human UI primary and the tool layer additive (CG draft; Chrome origin trial + ChatGPT desktop runtimes).

## [2026-09-28] ingest | mobile-state-map-marker-state-from-changing-position — knowledge-flush, 19 claimed rows: 1 new page (new mobile category `state`), 18 plan-gap rows retired as local-layer. New (verified): a maps-compose `Marker` whose position comes from changing state is built with `rememberUpdatedMarkerState(position)` inside `key(item.id) { … }`; `rememberMarkerState` is `rememberSaveable { MarkerState(position) }` so later values are ignored (deprecated since v6.4.3, PR #638; removed in 6.7.0 and restored deprecated in 6.7.1, PR #730), and remembered state is positional so a reloaded list hands one item another item's state (Compose lifecycle doc, `key`). Edge cases: draggable marker under a re-assigned position, `remember` vs configuration change, pre-6.4.3 pins. Reciprocal related link on process-death-and-state; INDEX.md mobile route line widened.
9 changes: 8 additions & 1 deletion wiki/mobile/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Route here for: app-side iOS/Android/cross-platform concerns — process lifecyc
and state survival, offline storage and sync, network calls from the device,
store releases/rollout/hotfix strategy, startup performance, deep-link/push
entry routing, modal presentation (sheets/covers contending for one host),
runtime permissions, and sensitive data stored on the device.
runtime permissions, sensitive data stored on the device, and in-composition
UI state identity (a Compose map marker driven by changing state).
Server-side API patterns (idempotency storage, retry contracts, error bodies)
stay in the backend domain (pages link there).

Expand All @@ -16,6 +17,12 @@ Match your situation to a "load when" line; load only matching pages.
|------|-----------|
| [process-death-and-state](lifecycle/process-death-and-state.md) | Building any screen with in-progress user state (forms, wizards, selections, media position) — choosing where each kind of state lives (saved-state vs local storage vs keychain/keystore); a bug report says "app lost my data when I switched apps"; designing deep-link/notification entry that must rebuild state on a cold process; writing a process-death test plan |

## state

| Page | Load when |
|------|-----------|
| [map-marker-state-from-changing-position](state/map-marker-state-from-changing-position.md) | A maps-compose `Marker` (Jetpack Compose on Google Maps) takes its position from state that changes after first composition — a draft pin following map taps, a tracked entity, a reloaded or reordered list of places; the marker stays at its first position, or one place shows another place's coordinates/info window after the list changes; choosing `rememberMarkerState` vs `rememberUpdatedMarkerState` vs `rememberSaveable(saver = MarkerState.Saver)`, and wrapping list markers in `key(item.id)` |

## offline

| Page | Load when |
Expand Down
2 changes: 1 addition & 1 deletion wiki/mobile/lifecycle/process-death-and-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sources:
- https://developer.android.com/privacy-and-security/keystore
- https://developer.apple.com/documentation/security/keychain-services
last_verified: 2026-07-10
related: [mobile-offline-offline-first-sync]
related: [mobile-offline-offline-first-sync, mobile-state-map-marker-state-from-changing-position]
---

# Surviving OS Process Death with User State Intact
Expand Down
83 changes: 83 additions & 0 deletions wiki/mobile/state/map-marker-state-from-changing-position.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
id: mobile-state-map-marker-state-from-changing-position
domain: mobile
category: state
applies_to: [android, jetpack-compose]
confidence: verified
sources:
- https://github.com/googlemaps/android-maps-compose/blob/main/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt
- https://googlemaps.github.io/android-maps-compose/maps-compose/com.google.maps.android.compose/remember-updated-marker-state.html
- https://googlemaps.github.io/android-maps-compose/maps-compose/com.google.maps.android.compose/remember-marker-state.html
- https://github.com/googlemaps/android-maps-compose/pull/638
- https://github.com/googlemaps/android-maps-compose/pull/730
- https://developer.android.com/develop/ui/compose/lifecycle
last_verified: 2026-09-28
related: [mobile-lifecycle-process-death-and-state]
---

# A Compose Map Marker Whose Position Comes From Changing State

## When this applies

A `Marker` (maps-compose, Jetpack Compose on Google Maps) takes its position
from state that changes after first composition: a draft pin that follows map
taps, a marker that tracks a moving entity, or a list of places that reloads
or reorders. The marker stays where it was first drawn, or one place shows up
at another place's coordinates after the list changes.

## Do this

1. **Read what the two constructors do.** `rememberMarkerState(position)` is
`rememberSaveable { MarkerState(position) }`: the argument is only the
starting point and every later value is ignored. Since v6.4.3 it is
`@Deprecated` with the message "It may be confusing to think that the state
is automatically updated as the position changes". `rememberUpdatedMarkerState(position)`
is `remember { MarkerState(position) }.also { it.position = position }`: it
re-assigns the position on every composition.
2. **Choose the constructor by whether the position can change:**

| Case | Do |
|------|----|
| Position is derived from state that changes (draft pin, tracked entity, reloaded list) | `rememberUpdatedMarkerState(position = item.position)` |
| Position never changes after first draw and only the marker's own drag/info-window state matters | `rememberSaveable(saver = MarkerState.Saver) { MarkerState(position) }` (the deprecation's own `ReplaceWith`) |
| The marker is user-draggable and its position is the source of truth | Hoist the position: keep it in your own state, feed it to `rememberUpdatedMarkerState`, and write it back when `MarkerState.isDragging` turns false (read `position` then) — see Edge cases |

3. **Wrap each marker of a list in `key(item.id) { … }`.** Remembered state is
bound to its call site plus execution order; when a list adds, removes or
reorders items, the state remembered in slot N stays in slot N and a
different place inherits it. `key` tells the runtime which values identify
the instance so the state follows the item. (`LazyColumn`'s `items(key = …)`
is the same mechanism; `GoogleMap` content has no built-in list key, so wrap
the loop body yourself.)
4. **Assert the behaviour, not the API name.** Change the state that feeds the
position and assert the marker's `position` equals the new value; reload the
list with an item removed from the front and assert each remaining marker's
position still matches its own item.

## Edge cases

| Case | Then |
|------|------|
| Draggable marker built with `rememberUpdatedMarkerState(position = fixed)` | Every recomposition re-assigns `position = fixed`, so a drag is undone on the next composition of that scope. Hoist the position into caller state and update it from the drag result, so the value passed in is the dragged one |
| State must survive configuration change or process death | `rememberUpdatedMarkerState` uses `remember`, not `rememberSaveable`; its API doc says it "cannot be used to preserve state across configuration changes". Keep the position in a `ViewModel` / `SavedStateHandle` and pass it in — the marker then re-reads it after recreation ([mobile-lifecycle-process-death-and-state]) |
| Library pinned to a version before v6.4.3 | `rememberUpdatedMarkerState` does not exist; write the same two-liner yourself: `remember { MarkerState(position) }.also { it.position = position }` |
| Version 6.7.0 | `rememberMarkerState` was removed from the artifact and restored (still deprecated) in v6.7.1 (PR #730); a build that stopped compiling on 6.7.0 needs 6.7.1+ or the `rememberSaveable` replacement |
| Marker's other state (info window shown, drag state) must survive a list reorder | `key(item.id)` keeps it with the item; without the key the info window opens on whichever item now occupies that slot |

## Instead of

| If you are about to | Do this instead | Why |
|---------------------|-----------------|-----|
| Pass a changing value to `rememberMarkerState(position = …)` and expect the marker to move | `rememberUpdatedMarkerState(position = …)` | The argument is only the initial value inside `rememberSaveable`; later values are ignored |
| Loop over a reloaded list and call `rememberUpdatedMarkerState` per item with no `key` | Wrap each item in `key(item.id) { … }` | Remembered state is positional; a shifted list gives one item another item's state |
| Set `markerState.position` from a `LaunchedEffect` to mirror a state value | Pass the value to `rememberUpdatedMarkerState` | That is exactly what the function does, on every composition, with no effect ordering to reason about |

## Sources

- https://github.com/googlemaps/android-maps-compose/blob/main/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt — `rememberMarkerState` = `rememberSaveable(key, MarkerState.Saver) { MarkerState(position) }` with the `@Deprecated` message quoted above; `rememberUpdatedMarkerState` = `remember { MarkerState(position) }.also { it.position = position }`; `MarkerState.position` is `mutableStateOf`
- https://googlemaps.github.io/android-maps-compose/maps-compose/com.google.maps.android.compose/remember-updated-marker-state.html — "updates the state value according to the update of the input parameter, like rememberUpdatedState. This cannot be used to preserve state across configuration changes"
- https://googlemaps.github.io/android-maps-compose/maps-compose/com.google.maps.android.compose/remember-marker-state.html — "this function does not automatically update the MarkerState when the input parameters change"; deprecation notice
- https://github.com/googlemaps/android-maps-compose/pull/638 — the deprecation and `rememberUpdatedMarkerState` (merge commit 0d6f023, first contained in tag v6.4.3, 2025-01-29; confirmed with the GitHub compare API)
- https://github.com/googlemaps/android-maps-compose/pull/730 — "bring rememberMarkerState back … The function is still deprecated" (v6.7.1)
- https://developer.android.com/develop/ui/compose/lifecycle — "When calling a composable multiple times from the same call site … the execution order is used in addition to the call site"; `key` composable and `LazyColumn` `items(key = …)`
- Field evidence 2026-09-28 (Android trip UI pinned to maps-compose 6.6.0): the two definitions were read in `maps-compose-6.6.0-sources.jar`, `Marker.kt:203-221` (same text as `main` today, re-fetched at tag v6.6.0), and the draft-pin and reloaded-place-list markers were built with `rememberUpdatedMarkerState` inside `key(item.id)`
Loading