fix(messaging): stabilize message paging queries to prevent duplicate LazyColumn keys (COLUMBA-9W)#1012
fix(messaging): stabilize message paging queries to prevent duplicate LazyColumn keys (COLUMBA-9W)#1012sentry[bot] wants to merge 1 commit into
Conversation
… LazyColumn keys (COLUMBA-9W)
Greptile SummaryAdds
Confidence Score: 4/5Safe to merge; the fix correctly stabilises OFFSET-based paging and eliminates the LazyColumn crash. One edge case around REPLACE-based re-insertion changing rowid is worth watching but does not affect the happy path. The two-line change is minimal and well-targeted. The rowid tiebreaker is the canonical SQLite solution for this class of pagination instability. The only subtle concern is that insertMessage/insertMessages use OnConflictStrategy.REPLACE, which performs a DELETE+INSERT and assigns a new rowid; this could reorder tied-timestamp messages after a bulk import or replay, though normal status-update flows use dedicated UPDATE queries and are unaffected. The only changed file is data/src/main/java/network/columba/app/data/db/dao/MessageDao.kt; the REPLACE conflict strategy on insertMessage and insertMessages is worth a second look if import/replay scenarios are exercised. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant UI as MessagingScreen (LazyColumn)
participant VM as ViewModel
participant Pager as Pager3
participant DAO as MessageDao
participant DB as SQLite (messages)
UI->>VM: collect pagingData
VM->>Pager: "flow<PagingData<MessageEntity>>"
Pager->>DAO: getMessagesForConversationPaged(peerHash, identityHash)
DAO->>DB: "SELECT * FROM messages WHERE ... ORDER BY COALESCE(receivedAt, timestamp) DESC, rowid DESC"
Note over DB: rowid DESC breaks timestamp ties,<br/>ensuring stable OFFSET pages
DB-->>DAO: deterministic, duplicate-free rows
DAO-->>Pager: PagingSource (stable pages)
Pager-->>VM: "PagingData<MessageEntity>"
VM-->>UI: pagingItems (unique message.id keys)
Note over UI: LazyColumn itemKey { message.id }<br/>no longer throws IllegalArgumentException
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant UI as MessagingScreen (LazyColumn)
participant VM as ViewModel
participant Pager as Pager3
participant DAO as MessageDao
participant DB as SQLite (messages)
UI->>VM: collect pagingData
VM->>Pager: "flow<PagingData<MessageEntity>>"
Pager->>DAO: getMessagesForConversationPaged(peerHash, identityHash)
DAO->>DB: "SELECT * FROM messages WHERE ... ORDER BY COALESCE(receivedAt, timestamp) DESC, rowid DESC"
Note over DB: rowid DESC breaks timestamp ties,<br/>ensuring stable OFFSET pages
DB-->>DAO: deterministic, duplicate-free rows
DAO-->>Pager: PagingSource (stable pages)
Pager-->>VM: "PagingData<MessageEntity>"
VM-->>UI: pagingItems (unique message.id keys)
Note over UI: LazyColumn itemKey { message.id }<br/>no longer throws IllegalArgumentException
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The
LazyColumninMessagingScreen.ktwas crashing with anIllegalArgumentException: Key "..." was already usedbecausepagingItems.itemKey { message -> message.id }received duplicatemessage.idvalues.Root cause analysis revealed that Room's offset-based
PagingSourcequeries (MessageDao.getMessagesForConversationPagedandgetMessagesForConversationPagedBySentTime) were emitting duplicate rows across page boundaries. This was due to theORDER BYclauses (COALESCE(receivedAt, timestamp) DESCandtimestamp DESC) lacking a unique tiebreaker, leading to unstable pagination when new messages were inserted or timestamps collided.This fix addresses the root cause by adding
rowid DESCas a secondary sort key to both affected paging queries inMessageDao. SQLite'srowidis a unique, monotonic identifier, ensuring deterministic and stable ordering even when primary sort keys are identical. This prevents thePagingSourcefrom emitting duplicatemessage.idvalues, thereby resolving theIllegalArgumentExceptionin theLazyColumnwhile preserving its stable key for smooth scrolling and animations.Fixes COLUMBA-9W