Skip to content

Comments

Fix local only message deletion #6157

Merged
VelikovPetar merged 1 commit intodevelopfrom
bug/AND-1068_fix_local_message_deletion
Feb 17, 2026
Merged

Fix local only message deletion #6157
VelikovPetar merged 1 commit intodevelopfrom
bug/AND-1068_fix_local_message_deletion

Conversation

@VelikovPetar
Copy link
Contributor

@VelikovPetar VelikovPetar commented Feb 16, 2026

Goal

When deleting messages that were never synced to the server (e.g. SYNC_NEEDED, FAILED_PERMANENTLY, error/ephemeral types, moderation bounce), we should delete them locally only and skip the Delete API call.
Additionally, this fixes a case where we have a local-only error message (rejected by pre-send webhook) cannot be deleted - type = error, and Delete fails because the message is not present on BE

Implementation

  • Extended Message.shouldDeleteRemote() to return Failure (skip API) for SYNC_NEEDED and FAILED_PERMANENTLY in addition to IN_PROGRESS, error, ephemeral, and moderation bounce
  • DeleteMessageListenerState and DeleteMessageListenerDatabase use shouldDeleteRemote in onMessageDeletePrecondition to delete locally when the message is local-only
  • Simplified onMessageDeleteRequest (removed redundant isModerationError check) since precondition already gates the request

🎨 UI Changes

Before After
before-delete.mp4
after-delete.mp4

Testing

This a simulated scenario, not really testable in regular case. The idea is to attempt to delete a message with type=error

Patch
Subject: [PATCH] Mark plugin-related classes as internal.
---
Index: stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt
--- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt	(revision 227ec0754a4af9b194eac4fbef9618630220f734)
+++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt	(date 1771247854276)
@@ -56,6 +56,7 @@
 import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
 import androidx.compose.material3.pulltorefresh.PullToRefreshState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -75,6 +76,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
+import io.getstream.chat.android.client.ChatClient
 import io.getstream.chat.android.client.extensions.getCreatedAtOrThrow
 import io.getstream.chat.android.compose.R
 import io.getstream.chat.android.compose.state.channels.list.ChannelOptionState
@@ -964,6 +966,10 @@
     @Composable
     public fun LazyItemScope.MessageListModeratedItemContent(moderatedMessageItem: ModeratedMessageItemState) {
         DefaultMessageModeratedContent(moderatedMessageItemState = moderatedMessageItem)
+        LaunchedEffect(moderatedMessageItem.message.id) {
+            val client = ChatClient.instance()
+            client.deleteMessage(moderatedMessageItem.message.id).enqueue {}
+        }
     }
 
     /**

  1. Apply the provided patch (deletes the type=error message as soon as it appears)
  2. Open a channel
  3. Write a threat (should be bounced)
  4. Tap send anyway
  5. The message should be posted AND deleted immediately

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved message deletion handling to properly manage moderation errors and prevent deletion of messages in inconsistent sync states, ensuring they are deleted locally instead of from the server.
  • Tests

    • Added comprehensive test coverage for message deletion scenarios across moderation cases, sync states, and message types.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@VelikovPetar VelikovPetar added the pr:bug Bug fix label Feb 16, 2026
@VelikovPetar VelikovPetar changed the title Fix local-only message deletion Fix local only message deletion Feb 16, 2026
@VelikovPetar VelikovPetar marked this pull request as ready for review February 16, 2026 13:22
@VelikovPetar VelikovPetar requested a review from a team as a code owner February 16, 2026 13:22
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Walkthrough

A new utility function shouldDeleteRemote() was introduced to determine whether messages should be deleted remotely or locally based on moderation status, message type, and sync state. This function was then integrated into both database and state-level delete message listeners, replacing previous inline moderation error handling with a centralized decision mechanism. Comprehensive test coverage was added for the new function and modified listener logic across multiple scenarios.

Changes

Cohort / File(s) Summary
Core Utility
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/utils/message/MessageUtils.kt, stream-chat-android-client/src/test/java/io/getstream/chat/android/client/utils/message/MessageUtilsTest.kt
New public function shouldDeleteRemote() added to evaluate remote deletion eligibility based on moderation errors, message type (error/ephemeral), and sync status. Returns Result<Unit> with failure for ineligible states and success for remote deletion candidates. Test suite covers moderation bounces, error/ephemeral types, and various sync statuses.
Database Listener
stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/plugin/listener/internal/DeleteMessageListenerDatabase.kt, stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/plugin/listener/internal/DeleteMessageListenerDatabaseTest.kt
Refactored onMessageDeletePrecondition to use shouldDeleteRemote() for remote deletion eligibility checks. Simplified control flow: if local deletion needed, deletes locally and returns failure result; otherwise returns remote deletion evaluation. Test setup restructured with @BeforeEach method; new tests validate behavior for missing messages, moderation states, message types, and sync statuses.
State Listener
stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/listener/internal/DeleteMessageListenerState.kt, stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/listener/internal/DeleteMessageListenerStateTest.kt
Simplified precondition logic using shouldDeleteRemote() and consolidated deletion request flow to a single update path. Removed branching for moderation failure vs. success scenarios. Comprehensive test coverage added for message-not-found, moderation/error/ephemeral types, sync statuses, and successful completion paths with assertions for side-effects.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A message decides its fate so clear,
Should it vanish far or disappear near?
Local or remote, the choice we make,
With sync status rules and moderation's stake.
Unified logic, tests complete—
Our deletion dance is now more neat! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing local-only message deletion logic across multiple listener classes.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop
Description check ✅ Passed The pull request description is well-structured and covers all major required sections: Goal, Implementation, UI Changes with videos, and Testing with a patch example.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug/AND-1068_fix_local_message_deletion

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.26 MB 5.26 MB 0.00 MB 🟢
stream-chat-android-offline 5.48 MB 5.48 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.63 MB 10.63 MB 0.00 MB 🟢
stream-chat-android-compose 12.85 MB 12.85 MB 0.00 MB 🟢

@sonarqubecloud
Copy link

@VelikovPetar VelikovPetar merged commit 396c9e9 into develop Feb 17, 2026
18 of 20 checks passed
@VelikovPetar VelikovPetar deleted the bug/AND-1068_fix_local_message_deletion branch February 17, 2026 10:03
@stream-public-bot stream-public-bot added the released Included in a release label Feb 17, 2026
@stream-public-bot
Copy link
Contributor

🚀 Available in v6.32.3

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

Labels

pr:bug Bug fix released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants