From 93b24572c8d6b03310be1963ad5d5c9d243d3de2 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:42:22 -0500 Subject: [PATCH] fix(messaging): use stored contact_key to avoid duplicate LazyColumn key crash (#6131) (#6142) Co-authored-by: Claude Opus 4.8 --- .../core/data/repository/PacketRepositoryImpl.kt | 4 ++-- .../meshtastic/core/repository/PacketRepository.kt | 4 ++-- .../messaging/ui/contact/ContactsViewModel.kt | 12 ++++-------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.kt index c708c34e6..8ea396944 100644 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.kt @@ -61,7 +61,7 @@ class PacketRepositoryImpl(private val dbManager: DatabaseProvider, private val .flatMapLatest { db -> db.packetDao().getContactKeys() } .map { map -> map.mapValues { it.value.data } } - override fun getContactsPaged(): Flow> = Pager( + override fun getContactsPaged(): Flow>> = Pager( config = PagingConfig( pageSize = CONTACTS_PAGE_SIZE, @@ -71,7 +71,7 @@ class PacketRepositoryImpl(private val dbManager: DatabaseProvider, private val pagingSourceFactory = { dbManager.currentDb.value.packetDao().getContactKeysPaged() }, ) .flow - .map { pagingData -> pagingData.map { it.data } } + .map { pagingData -> pagingData.map { it.contact_key to it.data } } override suspend fun getMessageCount(contact: String): Int = withContext(dispatchers.io) { dbManager.currentDb.value.packetDao().getMessageCount(contact) } diff --git a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketRepository.kt b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketRepository.kt index 377b75ad6..c9c6de056 100644 --- a/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketRepository.kt +++ b/core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketRepository.kt @@ -40,8 +40,8 @@ interface PacketRepository { /** Reactive flow of all conversation contacts, keyed by their contact identifier. */ fun getContacts(): Flow> - /** Reactive paged flow of conversation contacts. */ - fun getContactsPaged(): Flow> + /** Reactive paged flow of conversation contacts, each paired with its stored contact key. */ + fun getContactsPaged(): Flow>> /** Returns the total number of messages in a conversation. */ suspend fun getMessageCount(contact: String): Int diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/ContactsViewModel.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/ContactsViewModel.kt index 17bea1132..084d683bb 100644 --- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/ContactsViewModel.kt +++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/ui/contact/ContactsViewModel.kt @@ -142,17 +142,13 @@ class ContactsViewModel( val myNodeNum = params.myNodeNum packetRepository.getContactsPaged().map { pagingData -> - pagingData.map { packetData: DataPacket -> - // Determine if this is my message (originated on this device) + pagingData.map { (contactKey, packetData) -> + // Use the stored contact_key as the identity. Recomputing it here from a + // possibly-null myNodeNum collapses distinct conversations onto one key and + // crashes the LazyColumn with a duplicate-key error (#6131). val fromLocal = packetData.isFromLocal(myNodeNum) val toBroadcast = packetData.isBroadcast - // Reconstruct contactKey exactly as rememberDataPacket() computes it: - // For outgoing or broadcast: use the "to" field (recipient / ^all) - // For incoming DMs: use the "from" field (the other party) - val contactId = if (fromLocal || toBroadcast) packetData.to else packetData.from - val contactKey = "${packetData.channel}$contactId" - // grab usernames from NodeInfo val userId = if (fromLocal) packetData.to else packetData.from val user = nodeRepository.getUser(userId ?: NodeAddress.ID_BROADCAST)