Make repeated ids safe as lazy list keys (title page cast crash) #62
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fm/tv-dup-lazy-key"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Crash
Justin's crash report (ACRA upload from the installed round 2 Opt build 1.1.0-184-gbf2efcfd, Xiaomi MiTV-AFMU0, Android 14, armeabi-v7a), destination
title/079c13d9-7e77-d32c-07f3-eaea68a76001:java.lang.IllegalArgumentException: Key "cebe8282-34dd-72da-218f-5d15ad4ebab4" was already used. If you are using LazyColumn/Row please make sure you provide a unique key for each item.thrown in a Compose lazy-list measure pass on the main thread, no app frames (R8).Root cause
The key is a UUID, so it is a Jellyfin item/person id (not a catalogue key like
movie:123). On a held title page the only Wolfskin list keyed by a Jellyfin UUID that can repeat is the Cast row:TitleShelf.ktTitleCastusedkey = person.id, andTitleReadFacade.build(TitleReadFacade.kt:200) builds the cast fromitem.peoplefiltered to type Actor withperson.idas the id and no de-duplication. Jellyfin lists a person once per credit, so the same actor appearing twice (two roles, or a duplicated People entry) yields two cast items with the same id. The related shelf and the episode shelf (same file) used the sameitem.idpattern and had the same hazard in principle.I could not query the real item (no credentials), so I cannot confirm which of the three it was; the Cast row is the strongest match (a UUID that is a person id, a held title). All three are fixed, so the crash is closed whichever it was.
Fix
New
LazyKeys.kt:List<String>.uniqueKeys(). The first occurrence of an id keeps its plain key; each repeat gets a stableid#n. So focus/scroll restoration by key is unchanged for every list without a repeat, and repeats are kept visible (not dropped) because the focus/column index maps 1:1 onto the list and a person with two roles is legitimately shown for both. Applied by precomputing keys withrememberand keying by index.Audit of every keyed lazy list in the Wolfskin UI
Fixed (key could repeat):
TitleShelf.kt), More-like-this row and Episodes row (TitleMediaShelf), Season chips (TitlePage.kt)PersonPage.kt, row key wasrow.first().id; credits are distinct-by-id after paging, but a first-page repeat or the same title in two rows is cheap to cover)HomeShelf.kt): the merged resume + next-up lane can list the same episode from both sources; this is the card keyLibraryScreen.kt)ComingInSheet.kt)Checked and left alone:
HomeScreen.kt): fixed ids plus continuation lane ids; Discover lane ids are unique by construction (services and genre names are distinct-by-key before they become query ids)DiscoverScreen.kt): ids as above (pk-movie,pk-tv,pk-w-<seed>,svc,hidden, catalogue ids)DiscoverShelf.kt):item.keyistype:tmdb; every source is already de-duplicated (projectDiscoverLanesadds each key to a seen-set, catalogue lanes usedistinctBy { it.key }, hidden usesdistinctBy)distinctBy { it.id }in the providerheroes, crossfade keyed by title)CardGrid.ktupstream pattern is not Wolfskin UI) and Library header/toolbar/tail: fixed stringsapp/src/main/java/com/github/damontecres/wholphin, e.g. TabRow, ViewOptionsDialog): not part of the Wolfskin UIforEachIndexedRow, not a lazy listTests
DuplicateLazyKeysTest(wolfskin-design, Robolectric compose): composes each fixed list with duplicate ids and asserts it does not throw, plus a unit test ofuniqueKeys. Without the fix 7 of the 8 original tests fail with IllegalArgumentException (cast, related, episodes, season chips, filmography, home shelf, library grid; requests test also fails without its fix); with the fix all pass. Also passed with the change: RequestsKeysTest, LibraryKeysTest, HomeKeysTest. Not run, by instruction: fullmake test,make apk-debug, device.No visible UI change for any list without repeats; no frame proofs.
Delta review of the duplicate-lazy-key PR at head
64432b0032049fde109baa628514190cdf691b42.Code verdict: APPROVE (
review/code-claude= success).Design verdict: PASS (
review/design-claude= success). No blocking finding.What I checked:
uniqueKeys()keeps the first occurrence's plain key and gives each repeat a stableid#n. The output is always as long as the input, and a literala#1cannot collide (unit test:[a, b, a#1, a#2, a#1#1]).remember: cast, related and episodes (TitleShelf), season chips (TitlePage), filmography (PersonPage), the merged Continue watching lane (HomeShelf), Library grid rows (LibraryScreen,rowCountandrowKeysderived together) and the requests sheet (ComingInSheet). A key list can no longer be shorter than the item count, which was the bug in the earlier head6f85b9c6.grid-,season-N), so key-based focus and scroll restoration is unchanged. Repeats stay visible, so index-based focus maps still line up. There is no UI, token or motion change, andgenerate_tokens.py --checkpasses.DiscoverLaneProjection,ConductarrCatalogue,ConductarrProvider).DuplicateLazyKeysTestcompose each fixed list with duplicate ids and assert the repeats render. Before the fix, 7 of the original 8 failed withIllegalArgumentException, per the PR.git merge-treeagainst the Home PR (origin/fm/tv-r2-home), the Discover PR (origin/fm/tv-rd2-discover) and the trailer PR (origin/fm/tv-trailer-intent) is clean.Non-blocking notes:
id#ndepends on its order among repeats, so reordering duplicates swaps which card keeps which state. This only affects lists that already had duplicates.else { ... }wrappers inTitleShelfleave the inner block at its old indent (cosmetic).LibrarySpacingRegressionTest,MotionSurfaceTest) are left to the gate.Full report (delta reviews 3):
/root/.treehouse/firstmate-7bab20/2/firstmate/data/round2-review/report.md. Not run: Gradle/make, nothing on the box.Pull request closed