Skip to content

Write and send emoji reactions off the main thread - #2245

Draft
mpretty-cyro wants to merge 2 commits into
devfrom
fix/emoji-reaction-off-main
Draft

mpretty-cyro wants to merge 2 commits into
devfrom
fix/emoji-reaction-off-main

Conversation

@mpretty-cyro

@mpretty-cyro mpretty-cyro commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Contributor checklist

  • I have tested my contribution on these devices:
  • Virtual device: Pixel 6 profile, API 37 (arm64), play flavour, debug
  • My contribution is fully baked and ready to be merged as is
  • I ensure that all the open issues my contribution fixes are mentioned in the commit message of my first commit using the Fixes #1234 syntax

Description

ANR sample: main thread in ConversationReactionOverlay.handleUpEvent → ConversationActivityV2.sendEmojiReaction →
ReactionDatabase.addReaction → beginTransaction, waiting for a connection.

Adding and removing a reaction now do their database work and send in the app-wide scope on Dispatchers.IO, then reload the
conversation from lifecycleScope. The rate limiter and UI checks stay on the main thread.

They run one at a time on a shared lane (Dispatchers.IO.limitedParallelism(1)), so a removal can't be sent before the add it undoes. "React with any" looks up your existing reaction on that lane too, so it sees one that is still being written instead of adding the same emoji twice.

Testing: reproduced on an emulator with a debug-only hook (not in this PR) that holds the database write lock: picking a reaction ANR'd on dev with the main thread in sendEmojiReaction → ReactionDatabase.addReactions. With this change: no ANR, and the reaction was saved once the lock was released. Removing a reaction still works. The ordering on the shared lane is by construction; a removal overtaking its add wasn't reproduced on dev.

Picking a reaction wrote it to ReactionDatabase in a transaction, looked
up the community server ID and persisted the send job, all on the main
thread from the reaction overlay's touch handler. On a busy database
that froze the conversation until the app was killed for not responding.
Removing a reaction did the same. Both now run in the app-wide scope on
an IO thread, then reload the conversation.

@mpretty-cyro mpretty-cyro left a comment •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Each reaction ran in its own IO coroutine, so a removal could be sent
before the add it undoes. Run them on one shared lane, and look up the
existing reaction for "react with any" on that lane too, so it sees a
reaction still being written.

@mpretty-cyro mpretty-cyro left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant