From 6aecfe016ba4c1715741a7913d25e6d739bc0a85 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Apr 2026 15:34:22 +0000 Subject: [PATCH] =?UTF-8?q?perf(note=20types):=20second=20pass=20=E2=80=94?= =?UTF-8?q?=20fix=20more=20missing=20remember=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the first perf pass. Same goal: cut allocation cost for note rows that scroll inside LazyColumn feeds. - AppDefinition: key the `remember { tags.toImmutableListOfLists() }` block by `note` so it actually invalidates when the note changes - NIP90ContentDiscoveryResponse: drop the `remember(note) { Modifier.fillMaxWidth() }` wrapper — `Modifier.fillMaxWidth()` is a constant call - PeopleList: key the `derivedStateOf` for `name` by `noteEvent`, and switch `LaunchedEffect(Unit)` to `LaunchedEffect(noteEvent)` so the participants reload when the underlying event changes - PinList: replace `val pins by remember { mutableStateOf(noteEvent .pinnedEvents()) }` with `val pins = remember(noteEvent) { … }` — the `mutableStateOf` wrapper was unnecessary and the missing key meant `pins` could go stale on event updates - LongForm: return the `topics` list as `ImmutableList` so Compose treats it as a stable parameter to the consuming `forEach` - RelayList: drop the `mutableStateOf(RelayListCard(…))` wrap inside 4 `remember` blocks (DisplayRelaySet, DisplayNIP65RelayList write/ read, DisplayDMRelayList) — the value never changes after creation; also key by `noteEvent` rather than `baseNote`, and cache `noteEvent.description()` - Torrent: wrap `noteEvent.title() + totalSizeBytes()`, content comparison and `files().toImmutableList()` in `remember(noteEvent)` so they don't recompute and reallocate on every recomposition --- .../amethyst/ui/note/types/AppDefinition.kt | 2 +- .../amethyst/ui/note/types/LongForm.kt | 10 ++++- .../types/NIP90ContentDiscoveryResponse.kt | 3 +- .../amethyst/ui/note/types/PeopleList.kt | 4 +- .../amethyst/ui/note/types/PinList.kt | 2 +- .../amethyst/ui/note/types/RelayList.kt | 42 ++++++++----------- .../amethyst/ui/note/types/Torrent.kt | 25 ++++++----- 7 files changed, 47 insertions(+), 41 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt index acf1df765..469f6fa9d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt @@ -216,7 +216,7 @@ fun RenderAppDefinition( CreateTextWithEmoji( text = it, tags = - remember { + remember(note) { (note.event?.tags ?: emptyArray()).toImmutableListOfLists() }, fontWeight = FontWeight.Bold, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt index f36864286..6ba446027 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt @@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.grayText import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent +import kotlinx.collections.immutable.toImmutableList private const val WORDS_PER_MINUTE = 225 private val COVER_ASPECT_RATIO = 16f / 9f @@ -92,7 +93,14 @@ fun LongFormHeader( remember(noteEvent) { noteEvent.summary()?.ifBlank { null } ?: noteEvent.content.take(200).ifBlank { null } } - val topics = remember(noteEvent) { noteEvent.topics().distinct().take(3) } + val topics = + remember(noteEvent) { + noteEvent + .topics() + .distinct() + .take(3) + .toImmutableList() + } val readingMinutes = remember(noteEvent) { estimateReadingMinutes(noteEvent.content) } Column(MaterialTheme.colorScheme.replyModifier) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt index 5f73c4c0c..91ea6a7a4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt @@ -54,7 +54,6 @@ fun RenderNIP90ContentDiscoveryResponse( note = note, accountViewModel = accountViewModel, ) { - val modifier = remember(note) { Modifier.fillMaxWidth() } val tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList } @@ -62,7 +61,7 @@ fun RenderNIP90ContentDiscoveryResponse( content = noteEvent.content, canPreview = canPreview && !makeItShort, quotesLeft = quotesLeft, - modifier = modifier, + modifier = Modifier.fillMaxWidth(), tags = tags, backgroundColor = backgroundColor, id = note.idHex, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt index dbd7fb2d9..93abb98b3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt @@ -81,7 +81,7 @@ fun DisplayPeopleList( members.take(3) } - val name by remember { derivedStateOf { "#${noteEvent.titleOrName() ?: noteEvent.dTag()}" } } + val name by remember(noteEvent) { derivedStateOf { "#${noteEvent.titleOrName() ?: noteEvent.dTag()}" } } Text( text = name, @@ -95,7 +95,7 @@ fun DisplayPeopleList( textAlign = TextAlign.Center, ) - LaunchedEffect(Unit) { + LaunchedEffect(noteEvent) { accountViewModel.loadUsers(noteEvent.taggedUserIds()) { members = it } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PinList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PinList.kt index 105a7c898..9f19533c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PinList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PinList.kt @@ -65,7 +65,7 @@ fun RenderPinListEvent( ) { val noteEvent = baseNote.event as? PinListEvent ?: return - val pins by remember { mutableStateOf(noteEvent.pinnedEvents()) } + val pins = remember(noteEvent) { noteEvent.pinnedEvents() } var expanded by remember { mutableStateOf(false) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt index ca15807f1..6b1521866 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt @@ -75,12 +75,10 @@ fun DisplayRelaySet( ) { val noteEvent = baseNote.event as? RelaySetEvent ?: return - val relays by + val relays = remember(noteEvent) { - mutableStateOf( - RelayListCard( - noteEvent.relays().toImmutableList(), - ), + RelayListCard( + noteEvent.relays().toImmutableList(), ) } @@ -89,10 +87,12 @@ fun DisplayRelaySet( noteEvent.tags.firstTagValueFor("title", "name") ?: "#${noteEvent.dTag()}" } + val description = remember(noteEvent) { noteEvent.description() } + DisplayRelaySet( relays, relayListName, - noteEvent.description(), + description, backgroundColor, accountViewModel, nav, @@ -108,21 +108,17 @@ fun DisplayNIP65RelayList( ) { val noteEvent = baseNote.event as? AdvertisedRelayListEvent ?: return - val writeRelays by - remember(baseNote) { - mutableStateOf( - RelayListCard( - noteEvent.writeRelaysNorm() ?: emptyList(), - ), + val writeRelays = + remember(noteEvent) { + RelayListCard( + noteEvent.writeRelaysNorm() ?: emptyList(), ) } - val readRelays by - remember(baseNote) { - mutableStateOf( - RelayListCard( - noteEvent.readRelaysNorm() ?: emptyList(), - ), + val readRelays = + remember(noteEvent) { + RelayListCard( + noteEvent.readRelaysNorm() ?: emptyList(), ) } @@ -154,12 +150,10 @@ fun DisplayDMRelayList( ) { val noteEvent = baseNote.event as? ChatMessageRelayListEvent ?: return - val relays by - remember(baseNote) { - mutableStateOf( - RelayListCard( - noteEvent.relays(), - ), + val relays = + remember(noteEvent) { + RelayListCard( + noteEvent.relays(), ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt index 49ebe1cbb..de4f5c3ed 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt @@ -153,19 +153,24 @@ fun RenderTorrent( ) { val noteEvent = note.event as? TorrentEvent ?: return - val name = (noteEvent.title() ?: TorrentEvent.ALT_DESCRIPTION) - val size = " (" + countToHumanReadableBytes(noteEvent.totalSizeBytes()) + ")" - - val description = - if (noteEvent.content != name) { - noteEvent.content - } else { - null + val title = + remember(noteEvent) { + val name = noteEvent.title() ?: TorrentEvent.ALT_DESCRIPTION + val size = " (" + countToHumanReadableBytes(noteEvent.totalSizeBytes()) + ")" + name + size } + val description = + remember(noteEvent) { + val name = noteEvent.title() ?: TorrentEvent.ALT_DESCRIPTION + if (noteEvent.content != name) noteEvent.content else null + } + + val files = remember(noteEvent) { noteEvent.files().toImmutableList() } + DisplayFileList( - noteEvent.files().toImmutableList(), - name + size, + files, + title, description, noteEvent::toMagnetLink, backgroundColor,