fix: dedup node lists to prevent LazyColumn duplicate-key crash#6193
Conversation
The bare-numeric variant of the LazyColumn duplicate-key crash (Crashlytics
159e25351be7042db517f3af684684db; observed keys "0" and "2322") comes from two
lists keyed on a raw Int node num. num=0 is the unknown-node sentinel and a
brief double-emission during an active-DB switch can momentarily surface two
entries with the same num, tripping Compose's unique-key requirement.
Dedup with distinctBy at both render sites rather than switching to a
"num_index" composite key: NodeListScreen uses Modifier.animateItem() and
reorders, and index-based keys would break move animations (a moved item gets a
new key and is treated as remove+insert). Dedup keeps key = { it.num } stable
and unique.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
ChangesNode list rendering
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt`:
- Around line 238-242: Deduplicate the node list once before rendering so the
displayed count matches the rows. In NodeListScreen, compute a shared list using
distinctBy { it.num }, pass its size to MainAppBar, and reuse that list in the
LazyColumn items call while preserving key = { it.num } and animateItem()
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ce379c78-f3ee-45cb-b6b2-7b969266909b
📒 Files selected for processing (2)
androidApp/src/google/kotlin/org/meshtastic/app/map/component/ClusterItemsListDialog.ktfeature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
Hoist distinctBy { it.num } out of the LazyColumn items() call into a
remember(allNodes) val so the deduped list is the single source for the app-bar
count (nodes.size), the rendered rows, and the empty-state check — they can no
longer disagree — and it's computed per emission rather than per composition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Why
Crashlytics issue
159e25351be7042db517f3af684684dbis an umbrella forIllegalArgumentException: Key "X" was already usedcrashes inLazyColumn. Two variants are already fixed (contactKey via #6169, tcp-discovered via #6189). This closes out the third, untriaged variant: bare-numeric keys — observed"0"on 2.8.0-internal.4 and"2322"on 2.7.13.Both come from lists keyed on a raw
Intnode num:NodeListScreen—items(nodes, key = { it.num })ClusterItemsListDialog—items(items, key = { it.node.num })num = 0is the unknown-node sentinel, and a brief double-emission during an active-DB switch (SwitchingNodeInfoReadDataSource'sflatMapLatest) can momentarily surface two entries with the samenum, tripping Compose's unique-key requirement. The DB itself can't hold duplicate nums (numis the@PrimaryKey), so this is a transient render-boundary collision.🐛 Fix
Dedup with
distinctBy { it.num }at both render sites.Deliberately not the
"${num}_$index"composite-key pattern used elsewhere in the codebase for timestamp keys:NodeListScreenusesModifier.animateItem()and the list reorders (sort by last-heard/distance). Index-in-key changes when an item moves, which turns move animations into remove+insert. Dedup keepskey = { it.num }stable and unique, preserving the animation.Testing Performed
No regression test added — the fix is a stdlib
distinctByone-liner at the UI boundary with no custom list logic to unit-test (the collision only manifests through Compose'sitemskey path). Baseline verification passed locally:spotlessCheck detekt assembleDebug,:androidApp:testGoogleDebugUnitTest, and:feature:node:allTestsall green.🤖 Generated with Claude Code
Summary by CodeRabbit