From c89639f39c78e6f6dad19f2d06fa314eccb96769 Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 28 Oct 2026 18:25:31 +0530 Subject: [PATCH 01/42] docs(android): add Offline Support pages for the Android SDK v5.1.0 Adds an Offline Support group to the Android v5 SDK docs: overview, enable, show cached data, send while offline, stay in sync, media caching, encrypt the cache, and a configuration & API reference. Adds shared deprecation callouts for the two-phase fetch listeners and MessageSentListener, the nav group in docs.json and the llms index entries. Co-Authored-By: Claude Opus 5.5 --- docs.json | 13 + sdk/android/v5/llms-android-v5.mdx | 10 + sdk/android/v5/offline-cached-data.mdx | 339 +++++++++++++++ sdk/android/v5/offline-enable.mdx | 208 ++++++++++ sdk/android/v5/offline-encryption.mdx | 176 ++++++++ sdk/android/v5/offline-media-caching.mdx | 239 +++++++++++ sdk/android/v5/offline-overview.mdx | 90 ++++ sdk/android/v5/offline-reference.mdx | 186 +++++++++ sdk/android/v5/offline-send.mdx | 410 +++++++++++++++++++ sdk/android/v5/offline-sync.mdx | 331 +++++++++++++++ snippets/sdk/offline-deprecated-listener.mdx | 3 + snippets/sdk/offline-deprecated-send.mdx | 3 + 12 files changed, 2008 insertions(+) create mode 100644 sdk/android/v5/offline-cached-data.mdx create mode 100644 sdk/android/v5/offline-enable.mdx create mode 100644 sdk/android/v5/offline-encryption.mdx create mode 100644 sdk/android/v5/offline-media-caching.mdx create mode 100644 sdk/android/v5/offline-overview.mdx create mode 100644 sdk/android/v5/offline-reference.mdx create mode 100644 sdk/android/v5/offline-send.mdx create mode 100644 sdk/android/v5/offline-sync.mdx create mode 100644 snippets/sdk/offline-deprecated-listener.mdx create mode 100644 snippets/sdk/offline-deprecated-send.mdx diff --git a/docs.json b/docs.json index 604e2521b..c3d3f8243 100644 --- a/docs.json +++ b/docs.json @@ -4125,6 +4125,19 @@ "sdk/android/v5/reactions" ] }, + { + "group": "Offline Support", + "pages": [ + "sdk/android/v5/offline-overview", + "sdk/android/v5/offline-enable", + "sdk/android/v5/offline-cached-data", + "sdk/android/v5/offline-send", + "sdk/android/v5/offline-sync", + "sdk/android/v5/offline-media-caching", + "sdk/android/v5/offline-encryption", + "sdk/android/v5/offline-reference" + ] + }, "sdk/android/v5/calling-overview", { "group": "Users", diff --git a/sdk/android/v5/llms-android-v5.mdx b/sdk/android/v5/llms-android-v5.mdx index 1aed991bb..239fd0137 100644 --- a/sdk/android/v5/llms-android-v5.mdx +++ b/sdk/android/v5/llms-android-v5.mdx @@ -67,6 +67,16 @@ long-tail methods, group/user management, calling, or edge-case listeners. - [Mentions](/sdk/android/v5/mentions) - [Reactions](/sdk/android/v5/reactions) +## Offline support +- [Offline Support — Overview](/sdk/android/v5/offline-overview) +- [Enable Offline Support](/sdk/android/v5/offline-enable) +- [Show Cached Data](/sdk/android/v5/offline-cached-data) +- [Send While Offline](/sdk/android/v5/offline-send) +- [Stay in Sync](/sdk/android/v5/offline-sync) +- [Media Caching](/sdk/android/v5/offline-media-caching) +- [Encrypt the Cache](/sdk/android/v5/offline-encryption) +- [Offline Configuration & API Reference](/sdk/android/v5/offline-reference) + ## Calling - [Calling — Overview](/sdk/android/v5/calling-overview) diff --git a/sdk/android/v5/offline-cached-data.mdx b/sdk/android/v5/offline-cached-data.mdx new file mode 100644 index 000000000..14a0d9dde --- /dev/null +++ b/sdk/android/v5/offline-cached-data.mdx @@ -0,0 +1,339 @@ +--- +title: "Show Cached Data" +sidebarTitle: "Show Cached Data" +description: "Render cached messages, conversations, and lists instantly and reconcile them against the server with the CometChat Android SDK." +--- + +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// Every read fires up to two phases: cached first (instant), then server. +val request = MessagesRequest.MessagesRequestBuilder().setUID("UID").setLimit(30).build() + +request.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Phase 1: local cache. Fires first, even when empty. Render immediately. + } + override fun onServerMessages(messages: List) { + // Phase 2: server. Replaces the same slice — reconcile, don't append. Never fires offline. + } + override fun onError(e: CometChatException) { + // Offline ends here, after the cached page, with ERROR_INTERNET_UNAVAILABLE. + } +}) + +// Single items (additive two-phase overloads) +CometChat.getConversation("UID", CometChatConstants.CONVERSATION_TYPE_USER, conversationDetailsListener) +CometChat.getMessageDetails(messageId, messageDetailsListener) +``` + + + +With offline support enabled, every list and lookup the SDK already offers answers from the on-device cache first and then from the server. Your UI paints instantly from the cache, then reconciles when the server replies. This page describes that contract, the reads it applies to, and how to merge the two phases. + +## The two-phase contract + +Each read fires **up to two** callbacks, always in this order: + + + +The SDK delivers a page from the local cache **first**, synchronously-fast. It fires **even when the page is empty** — an empty cached page is indistinguishable from a cache miss, by design. Render it right away. + + +After a successful server response, the SDK delivers the server's copy for the **same slice** and writes it through to the cache before the callback. Reconcile your displayed list against it. + + + +Two rules follow from this: + +- **Offline, the read ends with an error.** After the cached page, a read whose server phase cannot be produced because the device is offline settles with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)`. The call always settles — it never hangs. +- **The server phase never fires offline.** It fires only on a genuine server response. Never treat a cached page as "end of history"; an empty cached page only means the end of the *cached* window. + + + +With offline support **off**, these new listeners behave like the classic callback: the cached phase never fires, and the server result arrives in `onServer…` instead of `onSuccess`. Nothing changes until you call `enabled(true)` (see [Enable Offline Support](/sdk/android/v5/offline-enable)). Minimum version **5.1.0**. + + + +## Read messages + +`MessagesRequest` supports both directions. `fetchPrevious` pages toward older messages; `fetchNext` pages toward newer ones. Both take a `MessageFetchListener`. + + + + + +```java +MessagesRequest request = new MessagesRequest.MessagesRequestBuilder() + .setUID("UID") + .setLimit(30) + .build(); + +request.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + + @Override + public void onServerMessages(List messages) { + // Reconcile this slice against the cached page, keyed by message id. + } + + @Override + public void onError(CometChatException e) { + Log.e(TAG, "Fetch failed: " + e.getCode()); + } +}); +``` + + + + +```kotlin +val request = MessagesRequest.MessagesRequestBuilder() + .setUID("UID") + .setLimit(30) + .build() + +request.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(messages: List) { + // Reconcile this slice against the cached page, keyed by message id. + } + + override fun onError(e: CometChatException) { + Log.e(TAG, "Fetch failed: ${e.code}") + } +}) +``` + + + + + +## Read conversations + +`ConversationsRequest` pages in one direction with `fetchNext`, taking a `ConversationFetchListener`. + + + + + +```java +ConversationsRequest request = new ConversationsRequest.ConversationsRequestBuilder() + .setLimit(30) + .build(); + +request.fetchNext(new CometChat.ConversationFetchListener() { + @Override + public void onCachedConversations(List conversations) { + // Render the cached list immediately. + } + + @Override + public void onServerConversations(List conversations) { + // Reconcile, keyed by conversation id. + } + + @Override + public void onError(CometChatException e) { + Log.e(TAG, "Fetch failed: " + e.getCode()); + } +}); +``` + + + + +```kotlin +val request = ConversationsRequest.ConversationsRequestBuilder() + .setLimit(30) + .build() + +request.fetchNext(object : CometChat.ConversationFetchListener() { + override fun onCachedConversations(conversations: List) { + // Render the cached list immediately. + } + + override fun onServerConversations(conversations: List) { + // Reconcile, keyed by conversation id. + } + + override fun onError(e: CometChatException) { + Log.e(TAG, "Fetch failed: ${e.code}") + } +}) +``` + + + + + +## Read the other lists + +Every remaining list read gains the same two-phase overload. Each has its own listener with cached/server callbacks of the matching type plus `onError`. + +| Request | Fetch method(s) | Listener | Cached / server callback | +|---|---|---|---| +| `MessagesRequest` | `fetchPrevious`, `fetchNext` | `MessageFetchListener` | `onCachedMessages` / `onServerMessages` | +| `ConversationsRequest` | `fetchNext` | `ConversationFetchListener` | `onCachedConversations` / `onServerConversations` | +| `ThreadsRequest` | `fetchNext` | `ThreadsFetchListener` | `onCachedThreads` / `onServerThreads` | +| `UsersRequest` | `fetchNext` | `UserFetchListener` | `onCachedUsers` / `onServerUsers` | +| `BlockedUsersRequest` | `fetchNext` | `UserFetchListener` | `onCachedUsers` / `onServerUsers` | +| `GroupsRequest` | `fetchNext` | `GroupFetchListener` | `onCachedGroups` / `onServerGroups` | +| `GroupMembersRequest` | `fetchNext` | `GroupMemberFetchListener` | `onCachedGroupMembers` / `onServerGroupMembers` | +| `BannedGroupMembersRequest` | `fetchNext` | `BannedGroupMemberFetchListener` | `onCachedBannedGroupMembers` / `onServerBannedGroupMembers` | +| `ReactionsRequest` | `fetchPrevious`, `fetchNext` | `ReactionFetchListener` | `onCachedReactions` / `onServerReactions` | +| `NotificationFeedRequest` | `fetchNext` | `NotificationFeedFetchListener` | `onCachedNotificationFeedItems` / `onServerNotificationFeedItems` | + +The call shape is identical to the conversations example above — swap the request, the listener, and the callback names. + + + +```java +UsersRequest request = new UsersRequest.UsersRequestBuilder() + .setLimit(30) + .build(); + +request.fetchNext(new CometChat.UserFetchListener() { + @Override + public void onCachedUsers(List users) { /* render */ } + + @Override + public void onServerUsers(List users) { /* reconcile, keyed by uid */ } + + @Override + public void onError(CometChatException e) { Log.e(TAG, e.getCode()); } +}); +``` + + + + +```kotlin +val request = UsersRequest.UsersRequestBuilder() + .setLimit(30) + .build() + +request.fetchNext(object : CometChat.UserFetchListener() { + override fun onCachedUsers(users: List) { /* render */ } + + override fun onServerUsers(users: List) { /* reconcile, keyed by uid */ } + + override fun onError(e: CometChatException) { Log.e(TAG, e.code) } +}) +``` + + + + + + + +For `ReactionsRequest`, `fetchPrevious` and `fetchNext` are cached independently — each direction keeps its own cached pages. Users, groups, members, banned members, reactions, and the notification feed are served from a per-request snapshot rather than a shared store; per-builder details live on each request's own page and in the [Offline Configuration & API Reference](/sdk/android/v5/offline-reference). + + + +## Read a single item + +`getConversation` and `getMessageDetails` each add a two-phase overload. Unlike the list reads, the cached callback fires **only when the item is cached** — never with an empty or null object. A cache miss goes straight to the server phase online, or straight to `onError` offline. + + + + + +```java +CometChat.getConversation( + "UID", + CometChatConstants.CONVERSATION_TYPE_USER, + new CometChat.ConversationDetailsListener() { + @Override + public void onCachedConversation(Conversation conversation) { + // Fires only on a cache hit. + } + + @Override + public void onServerConversation(Conversation conversation) { + // Server copy, written through before delivery. + } + + @Override + public void onError(CometChatException e) { + Log.e(TAG, "Lookup failed: " + e.getCode()); + } + }); + +CometChat.getMessageDetails(messageId, new CometChat.MessageDetailsListener() { + @Override + public void onCachedMessage(BaseMessage message) { /* cache hit only */ } + + @Override + public void onServerMessage(BaseMessage message) { /* server copy */ } + + @Override + public void onError(CometChatException e) { Log.e(TAG, e.getCode()); } +}); +``` + + + + +```kotlin +CometChat.getConversation( + "UID", + CometChatConstants.CONVERSATION_TYPE_USER, + object : CometChat.ConversationDetailsListener() { + override fun onCachedConversation(conversation: Conversation) { + // Fires only on a cache hit. + } + + override fun onServerConversation(conversation: Conversation) { + // Server copy, written through before delivery. + } + + override fun onError(e: CometChatException) { + Log.e(TAG, "Lookup failed: ${e.code}") + } + }) + +CometChat.getMessageDetails(messageId, object : CometChat.MessageDetailsListener() { + override fun onCachedMessage(message: BaseMessage) { /* cache hit only */ } + + override fun onServerMessage(message: BaseMessage) { /* server copy */ } + + override fun onError(e: CometChatException) { Log.e(TAG, e.code) } +}) +``` + + + + + +## Merge the two phases + +The server phase **replaces the same slice** — it is a reconcile, not a second batch. Render the cached page straight away, then on the server phase diff it into your displayed list keyed by the item's stable id (message id, conversation id, uid, guid). Do not append the server page to the cached one. + + + +Key your list by id so the two phases merge cleanly: update items that changed, add new ones, and drop cached items the server slice omits. To page further back, issue the next `fetchPrevious()` / `fetchNext()` rather than treating the server phase as more pages. + + + +## When the cached page is empty + +The cache answers a read **only if every filter on it is locally computable**. A single server-only predicate makes the entire cached phase return an empty list — with no error and no warning — while the server phase still answers in full. The guiding rule is *better empty than wrong*. + +The most common server-only predicate is `searchKeyword`, which blanks the cached phase on every request. Other examples include `hasLinks`, `updatesOnly`, and `interactionGoalCompletedOnly` on `MessagesRequest`, and the presence filters (`UsersRequest.setUserStatus`, `GroupMembersRequest.setStatus`) on the entity lists. + + + +An offline-critical screen must use only cache-supported filters — one innocuous builder flag can silently disable the cached phase for a whole screen. The full per-builder matrix of what the cache answers lives in the [Offline Configuration & API Reference](/sdk/android/v5/offline-reference). + + diff --git a/sdk/android/v5/offline-enable.mdx b/sdk/android/v5/offline-enable.mdx new file mode 100644 index 000000000..0e6216cba --- /dev/null +++ b/sdk/android/v5/offline-enable.mdx @@ -0,0 +1,208 @@ +--- +title: "Enable Offline Support" +sidebarTitle: "Enable Offline Support" +description: "Turn on local caching and the durable send outbox, and verify offline support is active with the CometChat Android SDK." +--- + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// 1. Build an OfflineConfig (off by default — call enabled(true)). +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .build() + +// 2. Attach it to AppSettings. +val appSettings = AppSettings.AppSettingsBuilder() + .setRegion("REGION") + .setOfflineConfig(offlineConfig) // explicit config; enabled value wins + // .offline(true) // shorthand: enable with all defaults + .build() + +// 3. Pass the settings to init. +CometChat.init(context, "APP_ID", appSettings, callbackListener) + +// 4. Verify it is active for the logged-in user. +val active = CometChat.isOfflineEnabled() // Boolean +``` + + + +Offline support is **off by default**. You turn it on once, at initialization, by attaching an `OfflineConfig` to your `AppSettings`. Until you do, the SDK behaves exactly as before. This page covers the minimal setup; for every tunable option see the [Configuration & API Reference](/sdk/android/v5/offline-reference). + + + + +Offline support requires CometChat Android SDK **5.1.0** or later (minSdk 21). Add or update the dependency: + + + +```groovy +implementation "com.cometchat:chat-sdk-android:5.1.0" +``` + + +```kotlin +implementation("com.cometchat:chat-sdk-android:5.1.0") +``` + + + +If you are new to the SDK, complete the [standard setup](/sdk/android/v5/setup) first; the steps below only add offline configuration to your existing `init`. + + + +Construct an `OfflineConfig` with its fluent `Builder` and call `enabled(true)`. Every other field has a sensible default, so set only what you need. + + + +```java +OfflineConfig offlineConfig = new OfflineConfig.Builder() + .enabled(true) + .build(); +``` + + +```kotlin +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .build() +``` + + + + + +Pass the config to `AppSettingsBuilder.setOfflineConfig(...)`. To enable with all defaults instead of building a config, use the `offline(boolean)` shorthand. + + + +```java +AppSettings appSettings = new AppSettings.AppSettingsBuilder() + .setRegion("REGION") + .setOfflineConfig(offlineConfig) // explicit config + // .offline(true) // shorthand: enable with defaults + .build(); +``` + + +```kotlin +val appSettings = AppSettings.AppSettingsBuilder() + .setRegion("REGION") + .setOfflineConfig(offlineConfig) // explicit config + // .offline(true) // shorthand: enable with defaults + .build() +``` + + + + +If you call both `setOfflineConfig(...)` and `offline(boolean)`, the explicit `OfflineConfig` wins — its `enabled` value is authoritative. + + + + +Pass the settings to `CometChat.init(...)` as you do today. No other call changes. + + + +```java +CometChat.init(context, "APP_ID", appSettings, new CometChat.CallbackListener() { + @Override + public void onSuccess(String successMessage) { + Log.d(TAG, "Initialization completed successfully"); + } + + @Override + public void onError(CometChatException e) { + Log.e(TAG, "Initialization failed: " + e.getMessage()); + } +}); +``` + + +```kotlin +CometChat.init(context, "APP_ID", appSettings, object : CometChat.CallbackListener() { + override fun onSuccess(successMessage: String?) { + Log.d(TAG, "Initialization completed successfully") + } + + override fun onError(e: CometChatException?) { + Log.e(TAG, "Initialization failed: ${e?.message}") + } +}) +``` + + + + + +Call `CometChat.isOfflineEnabled()`. It returns `true` only while offline support is actually active for the logged-in user — so it is `false` before anyone logs in, even when you enabled it in `AppSettings`. Read it at the moment of each decision and keep no copy of the result. + + + +```java +boolean active = CometChat.isOfflineEnabled(); +Log.d(TAG, "Offline support active: " + active); +``` + + +```kotlin +val active = CometChat.isOfflineEnabled() +Log.d(TAG, "Offline support active: $active") +``` + + + + + + +## Enable from a settings file + +If you initialize with `CometChat.initFromSettings(...)`, which reads `cometchat-settings.json` from your app's `assets/`, add an `offline` key under `chatSDK`. It accepts a boolean shorthand or a full configuration object. + +```json cometchat-settings.json +{ + "appId": "APP_ID", + "region": "REGION", + "chatSDK": { + "offline": true + } +} +``` + + + +```java +CometChat.initFromSettings(context, new CometChat.CallbackListener() { + @Override + public void onSuccess(String successMessage) { + Log.d(TAG, "Initialization completed successfully"); + } + + @Override + public void onError(CometChatException e) { + Log.e(TAG, "Initialization failed: " + e.getMessage()); + } +}); +``` + + +```kotlin +CometChat.initFromSettings(context, object : CometChat.CallbackListener() { + override fun onSuccess(successMessage: String?) { + Log.d(TAG, "Initialization completed successfully") + } + + override fun onError(e: CometChatException?) { + Log.e(TAG, "Initialization failed: ${e?.message}") + } +}) +``` + + + +## Next steps + +Offline support is now active for the logged-in user. Continue with [Show Cached Data](/sdk/android/v5/offline-cached-data) to adopt the new fetch listeners, and [Send While Offline](/sdk/android/v5/offline-send) for the durable outbox. Tune caps, TTLs, media, and encryption from the [Configuration & API Reference](/sdk/android/v5/offline-reference). diff --git a/sdk/android/v5/offline-encryption.mdx b/sdk/android/v5/offline-encryption.mdx new file mode 100644 index 000000000..a1ca00965 --- /dev/null +++ b/sdk/android/v5/offline-encryption.mdx @@ -0,0 +1,176 @@ +--- +title: "Encrypt the Cache" +sidebarTitle: "Encrypt the Cache" +description: "Encrypt the offline message cache at rest with SQLCipher, choose what happens when encryption cannot run, and inspect the live encryption status with the CometChat Android SDK." +--- + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// 1. Add the cipher engine to your app's build.gradle (the SDK does NOT bundle it): +// implementation 'net.zetetic:sqlcipher-android:4.13.0' + +// 2. Turn encryption on in OfflineConfig (off by default) +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .encryption(true) // encrypt the cache database at rest + .encryptionFailureBehavior(EncryptionFailureBehavior.ONLINE_ONLY) // default; never writes plaintext + .build() + +// 3. Inspect what encryption is actually doing +val status = CometChat.getOfflineEncryptionStatus() +status.isEnabled // what was configured +status.atRest // "SQLCIPHER" or "NONE" +status.keyStorage // "KEYSTORE" or "NONE" +status.isCipherAvailable // is the SQLCipher engine present in this build? +status.cipher // engine cipher_version once proven, else null +status.leakage // what is never covered (media files on disk) +``` + + + +Offline support keeps the logged-in user's messages in a local database on the device. Turn encryption on to store that database **encrypted at rest** using [SQLCipher](https://www.zetetic.net/sqlcipher/). Encryption is **opt-in** and off by default; nothing changes until you call `encryption(true)`. The minimum SDK version is **5.1.0**. + + +Encryption covers the **message database only**. Downloaded media files (photos, voice notes, documents) stay as ordinary unencrypted files on disk, because their local paths are handed to native image and video loaders. See [What is and isn't encrypted](#what-is-and-isn-t-encrypted). + + +## Add the SQLCipher dependency + +The SDK does **not** bundle the cipher engine, so apps that do not use encryption pay none of its size (roughly 3 MB per ABI) and the SDK `minSdk` stays 21. If you enable encryption you must add the engine yourself: + +```groovy +// app/build.gradle +dependencies { + implementation 'net.zetetic:sqlcipher-android:4.13.0' +} +``` + + +Without this dependency on the classpath the cache **cannot run encrypted**. What happens then is decided by [`EncryptionFailureBehavior`](#choose-what-happens-when-encryption-cannot-run) — by default the session runs server-only (no local cache). The SDK never creates a database that is labelled encrypted but is not. + + +## Turn on encryption + +Set `encryption(true)` on the `OfflineConfig` you pass to the SDK during initialization (see [Enable Offline Support](/sdk/android/v5/offline-enable)). + + + +```java +OfflineConfig offlineConfig = new OfflineConfig.Builder() + .enabled(true) + .encryption(true) + .encryptionFailureBehavior(EncryptionFailureBehavior.ONLINE_ONLY) + .build(); +``` + + + +```kotlin +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .encryption(true) + .encryptionFailureBehavior(EncryptionFailureBehavior.ONLINE_ONLY) + .build() +``` + + + +## Choose what happens when encryption cannot run + +`encryptionFailureBehavior` decides what the cache does when encryption **cannot be applied at all** on this build or device — for example the SQLCipher dependency is missing, the native library will not load, or there is no supported ABI. It governs only that case: a transient failure still retries, and a lost or wrong key self-heals by wiping and rebuilding the cache **encrypted**. + +| Value | Behavior | Writes plaintext? | +|---|---|---| +| `ONLINE_ONLY` *(default)* | Do not open a local cache at all — the session runs server-only. Makes a setup mistake visible: offline simply does not work until the dependency is added. | No | +| `PLAINTEXT_FALLBACK` | Open an **unencrypted** cache so offline keeps working, and log the downgrade at error level. | Yes | + + +`PLAINTEXT_FALLBACK` means cached message content may be stored **unencrypted** on disk whenever encryption cannot run. This availability-over-guarantee trade is wrong for compliance use — keep the default `ONLINE_ONLY` if cached content must never be written in the clear. + + +## Key storage + +You do not supply or manage the encryption key. The SDK derives a per-user key (scoped to the app ID and the logged-in user's UID), wraps it with a non-exportable **Android Keystore** key, and stores the wrapped blob in the app's no-backup files directory. The key never leaves the device and is never returned by any API. If the key is lost or the stored database no longer matches it, the cache resets and rebuilds — see [Cache resets](#cache-resets-on-key-loss). + +## Check encryption status + +`CometChat.getOfflineEncryptionStatus()` returns an `OfflineEncryptionStatus` describing what encryption is **actually** doing — never what was merely requested. It is safe to call at any time and never returns the key or any part of it. + + + +```java +OfflineEncryptionStatus status = CometChat.getOfflineEncryptionStatus(); +Log.d(TAG, "enabled=" + status.isEnabled() + + " atRest=" + status.getAtRest() + + " keyStorage=" + status.getKeyStorage() + + " cipherAvailable=" + status.isCipherAvailable() + + " cipher=" + status.getCipher()); +``` + + + +```kotlin +val status = CometChat.getOfflineEncryptionStatus() +Log.d(TAG, "enabled=${status.isEnabled}" + + " atRest=${status.atRest}" + + " keyStorage=${status.keyStorage}" + + " cipherAvailable=${status.isCipherAvailable}" + + " cipher=${status.cipher}") +``` + + + +| Field | Type | Meaning | +|---|---|---| +| `isEnabled()` | `boolean` | What was **configured** (`OfflineConfig.encryption`), not what was achieved. | +| `getAtRest()` | `String` | Coverage actually achieved: `"SQLCIPHER"` once an encrypted open is proven, otherwise `"NONE"`. | +| `getKeyStorage()` | `String` | `"KEYSTORE"` when a key was resolved from the Android Keystore; `"NONE"` when this session holds no key. Read `NONE` as "no key in use", not "no key exists". | +| `isCipherAvailable()` | `boolean` | Whether the SQLCipher engine is present and linked in this process. `false` when encryption is off or the optional dependency is missing / won't load. | +| `getCipher()` | `String` | The engine's `cipher_version` once an encrypted open is proven, else `null`. | +| `getLeakage()` | `String` | What encryption at rest never covers — on Android, the media files stored outside the database. | + + +The single most useful support signal is `isEnabled() == true` with `isCipherAvailable() == false`: encryption was switched on but could not run. On Android that almost always means the `net.zetetic:sqlcipher-android` dependency was never added to the app. + + +## What is and isn't encrypted + +| Data | Encrypted at rest? | +|---|---| +| Message database (messages, conversations, metadata, the pending-send outbox) | Yes, when `atRest` is `SQLCIPHER` | +| Downloaded media files on disk (images, video, audio, documents) | **No** — stored as ordinary files outside the database | + +Media bytes live outside the database because `Attachment.getLocalPath()` is public API and those paths are passed straight to native media loaders. "Encrypted at rest" here always means the message database, never the media cache. + +## Cache resets on key loss + +If the key can no longer decrypt the stored database, the SDK wipes the local cache and rebuilds it **encrypted** — it never silently falls back to plaintext for a cache that could be encrypted. Any unsent messages still queued in the outbox at that point are lost and each is failed through its send listener. + +Register an `OfflineCacheResetListener` to be told when this happens: + + + +```java +CometChat.addOfflineCacheResetListener("LISTENER_ID", new OfflineCacheResetListener() { + @Override + public void onOfflineCacheReset(OfflineCacheResetReason reason, int pendingMessagesLost) { + Log.w(TAG, "Cache reset: " + reason + ", pending lost=" + pendingMessagesLost); + } +}); +``` + + + +```kotlin +CometChat.addOfflineCacheResetListener("LISTENER_ID", object : OfflineCacheResetListener() { + override fun onOfflineCacheReset(reason: OfflineCacheResetReason, pendingMessagesLost: Int) { + Log.w(TAG, "Cache reset: $reason, pending lost=$pendingMessagesLost") + } +}) +``` + + + +The encryption-related reasons are `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, and `ENCRYPTION_CHANGED` (you turned encryption on or off between launches). See [Stay in Sync](/sdk/android/v5/offline-sync) for the full cache-reset flow and the complete list of reasons, and the [API Reference](/sdk/android/v5/offline-reference) for every field and enum. diff --git a/sdk/android/v5/offline-media-caching.mdx b/sdk/android/v5/offline-media-caching.mdx new file mode 100644 index 000000000..fe089656d --- /dev/null +++ b/sdk/android/v5/offline-media-caching.mdx @@ -0,0 +1,239 @@ +--- +title: "Media Caching" +sidebarTitle: "Media Caching" +description: "Cache image, video, audio and file attachments on the device so media stays viewable offline with the CometChat Android SDK." +--- + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// Set the media caching mode at init (fixed for the session) +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .mediaCaching(MediaCachingMode.AUTO) // AUTO (default) or OFF + .maxMediaCacheSize(500L * 1024 * 1024) // bytes; 0 = unlimited + .build() + +// Read the mode in effect +val mode: MediaCachingMode = CometChat.getMediaCachingMode() // OFF when offline is not active + +// Resolve cached bytes for an attachment +val attachment = mediaMessage.attachment +val localPath: String? = attachment.localPath // hydrated automatically on reads +val cached: String? = CometChat.getCachedMediaPath(attachment) // null if not cached + +// Download on demand +CometChat.downloadMedia(attachment, object : MediaCallbackListener { + override fun onSuccess(localPath: String?) { } + override fun onProgress(percent: Int) { } + override fun onError(e: CometChatException?) { } +}) + +// Observe background (AUTO) downloads +CometChat.addMediaDownloadListener("MEDIA_LISTENER", object : MediaDownloadListener { + override fun onMediaDownloaded(messageLocalId: String?, attachmentId: String?, localPath: String?) { } + override fun onMediaDownloadFailed(messageLocalId: String?, attachmentId: String?, error: CometChatException?) { } +}) +``` + + + +With offline support on, the SDK can cache attachment bytes durably on the device so media stays viewable offline — even after the server's presigned URL has expired. This page covers the caching modes, what downloads automatically, how to read cached bytes, and how to download media on demand. + + + +Media caching applies only while offline support is active for the logged-in user (`CometChat.isOfflineEnabled()` is `true`). See [Enable Offline Support](/sdk/android/v5/offline-enable). + + + +## Caching modes + +The caching mode is set once, in the `OfflineConfig` you pass at initialization, and is fixed for the SDK session. There is no runtime switch — to change it, re-initialize the SDK with a new config. + +| `MediaCachingMode` | Behavior | +|---|---| +| `AUTO` (default) | Incoming media bytes are eagerly downloaded and cached on any network, then LRU-managed. The outgoing copy is retained after the server acknowledges the send. | +| `OFF` | Incoming media bytes are **not** cached. The message metadata (including `fileUrl`) is still cached, so the message appears offline and the UI loads media live from the URL when online. The outgoing copy is dropped after the send is acknowledged. | + +Sending media always works in both modes. + + + +```java +OfflineConfig offlineConfig = new OfflineConfig.Builder() + .enabled(true) + .mediaCaching(MediaCachingMode.AUTO) + .maxMediaCacheSize(500L * 1024 * 1024) + .build(); +``` + + + + +```kotlin +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .mediaCaching(MediaCachingMode.AUTO) + .maxMediaCacheSize(500L * 1024 * 1024) + .build() +``` + + + + +Read the mode in effect at runtime with `CometChat.getMediaCachingMode()`. It returns `MediaCachingMode.OFF` whenever offline support is not active, whatever the app configured, so a UI layer can rely on it without reading the config itself. + +## What downloads automatically + +In `AUTO` mode, media bytes are downloaded and cached automatically on the two live receive paths: + +- a **realtime** media message received while connected, and +- opening a chat **online** (the server read refreshes and caches its media). + +A media message that arrives through a reconnect catch-up [sync](/sdk/android/v5/offline-sync) is cached as **metadata only** and renders as a placeholder; its bytes download lazily the next time you open that chat online. This holds in both `AUTO` and `OFF`. + +In `OFF` mode, nothing is auto-downloaded on any path; media loads live from its URL when online. + +## Reading cached media + +When bytes are already cached, the SDK **hydrates the local path automatically** on every message it delivers — you do not have to ask. Read it straight off the attachment: + + + +```java +Attachment attachment = mediaMessage.getAttachment(); +String localPath = attachment.getLocalPath(); // non-null when bytes are cached +if (localPath != null) { + // Render directly from disk — no network fetch. +} +``` + + + + +```kotlin +val attachment = mediaMessage.attachment +val localPath = attachment.localPath // non-null when bytes are cached +if (localPath != null) { + // Render directly from disk — no network fetch. +} +``` + + + + +To look up the cached path explicitly, call `getCachedMediaPath`. It is a cheap filesystem check, safe on the main thread, and returns `null` when the bytes are not cached (call `downloadMedia` to fetch them). + + + +```java +String path = CometChat.getCachedMediaPath(attachment); // null if not cached +``` + + + + +```kotlin +val path = CometChat.getCachedMediaPath(attachment) // null if not cached +``` + + + + + + +Each attachment of a multi-attachment message resolves independently. An attachment with no cached bytes keeps `localPath == null` and the UI loads it from the URL as before. + + + +## Downloading on demand + +Call `downloadMedia` to fetch and cache an attachment's bytes — for example when the user taps a placeholder. The SDK runs the resolution off the main thread (cached bytes, then a valid URL, then a re-signed URL if the presigned one expired) and delivers on the main thread. + + + +```java +CometChat.downloadMedia(attachment, new MediaCallbackListener() { + @Override + public void onSuccess(String localPath) { + // Bytes cached; render from localPath. + } + + @Override + public void onProgress(int percent) { + // 0..100, best-effort. + } + + @Override + public void onError(CometChatException e) { } +}); +``` + + + + +```kotlin +CometChat.downloadMedia(attachment, object : MediaCallbackListener { + override fun onSuccess(localPath: String?) { + // Bytes cached; render from localPath. + } + + override fun onProgress(percent: Int) { + // 0..100, best-effort. + } + + override fun onError(e: CometChatException?) { } +}) +``` + + + + +`onProgress(int percent)` reports `0..100`, best-effort and monotonic non-decreasing. It is emitted only when the server reports a content length; for an unknown-length download it is skipped and the call goes straight to `onSuccess`. Progress never blocks or fails the download. + +## Observe background downloads + +In `AUTO` mode, incoming media downloads in the background with no per-call callback. Register a `MediaDownloadListener` to learn when the bytes land (or fail) so a UI tile can refresh. All callbacks fire on the main thread. + + + +```java +CometChat.addMediaDownloadListener("MEDIA_LISTENER", new MediaDownloadListener() { + @Override + public void onMediaDownloaded(String messageLocalId, String attachmentId, String localPath) { + // messageLocalId: the message's server id (or local id if not yet sent) + // attachmentId: the attachment URL without its query string + // localPath: where the bytes landed + } + + @Override + public void onMediaDownloadFailed(String messageLocalId, String attachmentId, CometChatException error) { } +}); +``` + + + + +```kotlin +CometChat.addMediaDownloadListener("MEDIA_LISTENER", object : MediaDownloadListener { + override fun onMediaDownloaded(messageLocalId: String?, attachmentId: String?, localPath: String?) { + // messageLocalId: the message's server id (or local id if not yet sent) + // attachmentId: the attachment URL without its query string + // localPath: where the bytes landed + } + + override fun onMediaDownloadFailed(messageLocalId: String?, attachmentId: String?, error: CometChatException?) { } +}) +``` + + + + +Remove the listener with `CometChat.removeMediaDownloadListener("MEDIA_LISTENER")`. The background `MediaDownloadListener` carries no progress callback — progress is reported only on the explicit `downloadMedia` path above. + +## Media cache size limit + +Cached media bytes are bounded by `maxMediaCacheSize` (default **500 MB**; `0` means unlimited). When the cache exceeds the limit, the least recently accessed media is deleted first until it is back under budget. Media belonging to un-sent messages and in-progress uploads is never evicted and does not count toward the budget. + +Media is stored as files in durable, backup-excluded storage; the location is not configurable. Eviction runs silently in the background — there is no cache-limit callback. To reason about size, use `CometChat.getCacheSize()` and the configured limits. See the [configuration reference](/sdk/android/v5/offline-reference) for the full set of options. diff --git a/sdk/android/v5/offline-overview.mdx b/sdk/android/v5/offline-overview.mdx new file mode 100644 index 000000000..c91476ea1 --- /dev/null +++ b/sdk/android/v5/offline-overview.mdx @@ -0,0 +1,90 @@ +--- +title: "Offline Support" +sidebarTitle: "Offline Support" +description: "Understand how local caching, the send outbox, and reconnect catch-up keep chat working without a connection with the CometChat Android SDK." +--- + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// Offline support is OFF by default. Turn it on at init (minimum SDK 5.1.0). +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .build() + +val appSettings = AppSettings.AppSettingsBuilder() + .setRegion("REGION") + .setOfflineConfig(offlineConfig) // or .offline(true) for defaults + .build() + +CometChat.init(context, "APP_ID", appSettings, callbackListener) + +// Is offline support active right now for the logged-in user? +val active = CometChat.isOfflineEnabled() // Boolean +``` + + + +Offline support keeps the logged-in user's conversations and messages on the device so your app can show them instantly, lets the user send messages while disconnected, and catches up automatically when the connection returns. It is **off by default** — nothing changes in your app until you enable it ([Enable Offline Support](/sdk/android/v5/offline-enable)). This page explains the mental model; the other pages show the exact APIs. + + +Offline support requires CometChat Android SDK **5.1.0** or later. + + +## How it works + +Three ideas carry the whole feature. + + + +Every read goes through the local cache first. The SDK delivers the cached page immediately — even when it is empty — then fetches from the server and delivers the fresh result. Your UI renders what is on the device without waiting for the network, then updates when the server responds. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + + +A message sent while offline is saved to a durable outbox and reported as pending instead of failing. It survives app restarts and is sent when the connection returns. See [Send While Offline](/sdk/android/v5/offline-send). + + +When the device reconnects, the SDK syncs the conversations and messages that changed while it was away and flushes the outbox, so the local cache matches the server again. See [Stay in Sync](/sdk/android/v5/offline-sync). + + + +```mermaid +flowchart LR + A[App read] --> B[(Local cache)] + B -- cached page first --> A + B --> C{Online?} + C -- yes --> D[Server] + D -- fresh result --> A + C -- no --> E[onError:\nINTERNET_UNAVAILABLE] + F[Send while offline] --> G[(Durable outbox)] + G -- on reconnect --> D +``` + +## What changes in your app + +With offline support enabled, your fetch and send callbacks fire **twice** on the read path and gain a pending stage on the send path. To receive these cached and pending results you must move to the new listeners — `MessageFetchListener`, `ConversationFetchListener`, `MessageSentListener`, and the others listed on each page. The old `CallbackListener` overloads of the two-phase fetches and sends are deprecated in 5.1.0 but keep working. With offline support off, every API behaves exactly as it did before. + +## One cache per user + +The SDK keeps a single local cache for the user who is logged in. + +- Logging the **same** user back in reuses that cache, so the data is there instantly on next launch. +- Logging out, a session expiring, or logging in as a **different** user wipes the cache. You never clear it yourself — the SDK does it. + +See [Stay in Sync](/sdk/android/v5/offline-sync) for cache resets and logout behaviour. + +## How much is kept + +By default the cache is bounded by size, not age: it holds up to **100 conversations**, up to **500 messages per conversation**, around **250 MB** of data, and up to **500 MB** of cached media. Older entries are evicted first as these limits are reached. Every limit is tunable — see the [Configuration & API Reference](/sdk/android/v5/offline-reference). + +## Next steps + + + Turn it on and verify it is active. + The cached-first, server-next read contract. + The durable outbox and pending messages. + Reconnect catch-up, cache resets, logout. + Download and cache attachments for offline viewing. + Encrypt the local database at rest. + Every option, method, listener, enum, and error. + diff --git a/sdk/android/v5/offline-reference.mdx b/sdk/android/v5/offline-reference.mdx new file mode 100644 index 000000000..c3f027d0e --- /dev/null +++ b/sdk/android/v5/offline-reference.mdx @@ -0,0 +1,186 @@ +--- +title: "Offline Configuration & API Reference" +sidebarTitle: "Configuration & API Reference" +description: "A complete reference for the offline configuration options, public methods, listeners, enums, model fields, and error codes with the CometChat Android SDK." +--- + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// Build the config (every field has a default; set only what you change) +val offlineConfig = OfflineConfig.Builder() + .enabled(true) + .maxConversations(100) + .maxMessagesPerConversation(500) + .maxPendingMessages(50) + .mediaCaching(MediaCachingMode.AUTO) + .encryption(false) + .build() + +// Introspection / control +CometChat.isOfflineEnabled() // Boolean +CometChat.getMediaCachingMode() // MediaCachingMode +CometChat.getOfflineEncryptionStatus() // OfflineEncryptionStatus +CometChat.getSyncState() // SyncState +CometChat.getLastSyncTimestamp() // Long (server epoch seconds) +CometChat.getCacheSize() // Long (bytes) +CometChat.getPendingMessages(listener) // List +CometChat.forceSyncNow() // or forceSyncNow(callback) +CometChat.clearOfflineCache() +``` + + + +This page is the full surface of CometChat's offline support on Android: configuration options, public methods, listeners, enums, model fields, and error codes. The minimum SDK version is **5.1.0**, and offline support is **off by default**. For task-focused guides see [Enable Offline Support](/sdk/android/v5/offline-enable), [Show Cached Data](/sdk/android/v5/offline-cached-data), [Send While Offline](/sdk/android/v5/offline-send), [Stay in Sync](/sdk/android/v5/offline-sync), [Media Caching](/sdk/android/v5/offline-media-caching), and [Encrypt the Cache](/sdk/android/v5/offline-encryption). + +## OfflineConfig options + +Build with `OfflineConfig.Builder`. Every field is pre-set to its default, so call only the setters you want to change; the resulting `OfflineConfig` is immutable. Byte caps and the TTL treat `0` as **unlimited** (that dimension's eviction is disabled); the count caps are clamped and have no "unlimited". + +| Builder setter | Type | Default | Range / clamp | Effect | +|---|---|---|---|---| +| `enabled(boolean)` | `boolean` | `false` | — | Master gate for the whole offline engine. With it off the SDK behaves exactly as before. | +| `cacheDurationDays(int)` | `int` | `0` (unlimited) | `>= 0`; negative treated as `0` | Message retention TTL in days. `0` = no age-based prune; eviction is by the byte/count caps instead. | +| `maxStorageSize(long)` | `long` bytes | `262144000` (250 MB) | `0` = unlimited | Byte ceiling for the message/metadata database. | +| `maxMessagesPerConversation(int)` | `int` | `500` | clamped `[1, 1000]`; `0`/over → `1000` | Per-conversation cached-message window. | +| `maxConversations(int)` | `int` | `100` | clamped `[1, 1000]`; `0`/over → `1000` | Cached-conversation cap. | +| `maxPendingMessages(int)` | `int` | `50` | clamped `[1, 100]`; `0`/over → `100` | Pending-send outbox capacity. | +| `autoSendPendingOnReconnect(boolean)` | `boolean` | `true` | — | Auto-flush queued `PENDING` sends on reconnect. Gates only the outbox flush; the reconnect sync sweep runs regardless. | +| `pendingQueueOverflowPolicy(PendingMessageOverflowPolicy)` | enum | `REJECT_NEWEST` | — | What the outbox does when it is full. | +| `maxMediaCacheSize(long)` | `long` bytes | `524288000` (500 MB) | `0` = unlimited | Media-only byte budget (LRU). | +| `mediaCaching(MediaCachingMode)` | enum | `AUTO` | `null` → `AUTO` | Receive-side media mode. Fixed at init. | +| `encryption(boolean)` | `boolean` | `false` | — | Encrypt the cache database at rest. See [Encrypt the Cache](/sdk/android/v5/offline-encryption). | +| `encryptionFailureBehavior(EncryptionFailureBehavior)` | enum | `ONLINE_ONLY` | `null` → `ONLINE_ONLY` | What happens when encryption cannot run at all. | +| `maxSnapshotQueries(int)` | `int` | `100` | `>= 1`; `0`/negative → `100` | Most entity-list snapshots (users, groups, group members) kept for offline replay; LRU. | +| `snapshotTtlDays(int)` | `int` | `7` | `>= 1`; `0`/negative → `7` | Days an entity-list snapshot is kept after it was first recorded. | +| `debugTracing(boolean)` | `boolean` | `false` | — | Dev-only file trace of offline operations. Leave off in production. | + +Attach the config at init via `AppSettings.AppSettingsBuilder.setOfflineConfig(OfflineConfig)`, or enable with defaults via the `offline(boolean)` shorthand — see [Enable Offline Support](/sdk/android/v5/offline-enable). + +## Public methods on CometChat + +| Method | Returns | Purpose | +|---|---|---| +| `isOfflineEnabled()` | `boolean` | Whether offline support is active for the current session. | +| `getMediaCachingMode()` | `MediaCachingMode` | The effective media caching mode (`OFF` whenever offline is not active). | +| `getOfflineEncryptionStatus()` | `OfflineEncryptionStatus` | What encryption at rest is actually doing. | +| `getPendingMessages(CallbackListener>)` | `void` | All queued/failed messages in the outbox. | +| `retryMessage(String localMessageId, MessageSentListener)` | `void` | Retry one failed message by its local id. | +| `retryAllFailedMessages(MessageSentListener)` | `void` | Retry every failed message in the outbox. | +| `discardLocalMessage(String localMessageId, CallbackListener)` | `void` | Remove one unsent message from the outbox. | +| `forceSyncNow()` | `void` | Trigger a catch-up sync immediately. No-op when offline is not active. | +| `forceSyncNow(CallbackListener)` | `void` | Same, with a callback that reports why a manual sync did not start. | +| `getSyncState()` | `SyncState` | Whether a catch-up sweep is currently running. | +| `getLastSyncTimestamp()` | `long` | Server time of the last successful sync, in epoch seconds. | +| `clearOfflineCache()` | `void` | Wipe the local cache (and fail any unsent messages). | +| `getCacheSize()` | `long` | Current on-disk cache size in bytes. | +| `downloadMedia(Attachment, MediaCallbackListener)` | `void` | Download and cache an attachment's bytes. | +| `getCachedMediaPath(Attachment)` | `String` | Local path to already-cached bytes, or `null`. | +| `getExistingUploadFileRequest(String receiverId, String receiverType)` | `UploadFileRequest` | Re-attach to an in-progress offline upload. | +| `getExistingUploadFileRequest(String receiverId, String receiverType, long parentMessageId)` | `UploadFileRequest` | Same, for a thread parent. | + +### Listener registration + +| Method | Listener | +|---|---| +| `addMessageListener(String, MessageListener)` / `removeMessageListener(String)` | `MessageListener` (adds `onMessagesSynced`) | +| `addOfflineMessageListener(String, OfflineMessageListener)` / `removeOfflineMessageListener(String)` | `OfflineMessageListener` | +| `addSyncListener(String, SyncListener)` / `removeSyncListener(String)` | `SyncListener` | +| `addOfflineCacheResetListener(String, OfflineCacheResetListener)` / `removeOfflineCacheResetListener(String)` | `OfflineCacheResetListener` | +| `addMediaDownloadListener(String, MediaDownloadListener)` / `removeMediaDownloadListener(String)` | `MediaDownloadListener` | + +## Listeners and callbacks + +### Two-phase fetch listeners + +Each delivers the cached page first (when offline is active and a user is logged in), then the server result, then errors. With offline off the cached callback never fires. + +| Listener | Callbacks | +|---|---| +| `MessageFetchListener` | `onCachedMessages(List)`, `onServerMessages(List)`, `onError(CometChatException)` | +| `ThreadsFetchListener` | `onCachedThreads(List)`, `onServerThreads(List)`, `onError(CometChatException)` | +| `MessageDetailsListener` | `onCachedMessage(BaseMessage)`, `onServerMessage(BaseMessage)`, `onError(CometChatException)` | +| `ConversationFetchListener` | `onCachedConversations(List)`, `onServerConversations(List)`, `onError(CometChatException)` | +| `ConversationDetailsListener` | `onCachedConversation(Conversation)`, `onServerConversation(Conversation)`, `onError(CometChatException)` | +| `UserFetchListener` | `onCachedUsers(List)`, `onServerUsers(List)`, `onError(CometChatException)` | +| `GroupFetchListener` | `onCachedGroups(List)`, `onServerGroups(List)`, `onError(CometChatException)` | +| `GroupMemberFetchListener` | `onCachedGroupMembers(List)`, `onServerGroupMembers(List)`, `onError(CometChatException)` | +| `BannedGroupMemberFetchListener` | `onCachedBannedGroupMembers(List)`, `onServerBannedGroupMembers(List)`, `onError(CometChatException)` | +| `ReactionFetchListener` | `onCachedReactions(List)`, `onServerReactions(List)`, `onError(CometChatException)` | +| `NotificationFeedFetchListener` | `onCachedNotificationFeedItems(List)`, `onServerNotificationFeedItems(List)`, `onError(CometChatException)` | + +### Send, sync, cache, and media listeners + +| Listener | Callbacks | +|---|---| +| `MessageSentListener` | `onMessagePending(BaseMessage)`, `onMessageSent(BaseMessage)`, `onError(CometChatException)` | +| `OfflineMessageListener` | `onOutboxMessagePending(BaseMessage)`, `onOutboxMessageSent(BaseMessage)`, `onOutboxMessageFailed(BaseMessage, MessageFailureReason)` | +| `SyncListener` | `onConversationSyncStarted()`, `onConversationSyncCompleted(List)`, `onConversationSyncError(CometChatException)`, `onMessageSyncStarted()`, `onMessageSyncCompleted(List, SyncOutcome)`, `onMessageSyncError(CometChatException)` | +| `OfflineCacheResetListener` | `onOfflineCacheReset(OfflineCacheResetReason, int pendingMessagesLost)` | +| `MediaCallbackListener` | `onSuccess(String localPath)`, `onProgress(int percent)`, `onError(CometChatException)` | +| `MediaDownloadListener` | `onMediaDownloaded(String messageLocalId, String attachmentId, String localPath)`, `onMediaDownloadFailed(String messageLocalId, String attachmentId, CometChatException)` | +| `MessageListener` | `onMessagesSynced(List)` (offline addition to the existing listener) | + +## Enums + +| Enum | Values | +|---|---| +| `MediaCachingMode` | `AUTO` (auto-download + retain received media), `OFF` (skip auto-download; metadata still cached) | +| `PendingMessageOverflowPolicy` | `DROP_OLDEST` (evict the oldest pending to admit a newer send), `REJECT_NEWEST` (reject the new send) | +| `EncryptionFailureBehavior` | `ONLINE_ONLY` (server-only; never plaintext), `PLAINTEXT_FALLBACK` (open an unencrypted cache) | +| `MessageStatus` | `PENDING`, `SENDING`, `SENT`, `DELIVERED`, `READ`, `FAILED` | +| `MessageFailureReason` | `TRANSIENT` (retry likely helps), `PERMANENT` (retry won't help), `QUEUE_OVERFLOW` (evicted to admit a newer send), `QUEUE_FULL` (new send rejected, queue full), `BYTE_OVERFLOW` (media exceeded the byte cap), `DISCARDED` (removed before it could send) | +| `SyncOutcome` | `CAUGHT_UP` (gap replayed exactly), `PARTIAL` (a page was ingested but more remains) | +| `SyncState` | `IDLE`, `SYNCING` | +| `OfflineCacheResetReason` | `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, `ESCALATED` (unclassified failure recurred and was escalated), `ENCRYPTION_CHANGED` (the `encryption` setting changed between launches) | +| `UploadFileItemStatus.State` | `QUEUED`, `UPLOADING`, `UPLOADED`, `FAILED`, `REJECTED` | + +## Model fields + +| Field | Type | Meaning | +|---|---|---| +| `BaseMessage.getLocalMessageId()` | `String` | SDK-generated local handle for an optimistic/offline send; the stable key to reconcile the row when the server ack arrives. `null` for messages that never went through the outbox. | +| `BaseMessage.getStatus()` | `MessageStatus` | Lifecycle status of the message. | +| `Attachment.getLocalPath()` | `String` | Absolute device path to cached media bytes, or `null` when not cached. Never sent to the server. | + +## Error codes + +Compare `CometChatException.getCode()` against these codes. All are constants in `CometChatConstants.Errors` except `ERROR_PENDING_QUEUE_LIMIT` and `ERROR_OFFLINE_MESSAGE_PERSIST_FAILED`, which you compare as string values. + +| Code | Raised when | +|---|---| +| `ERROR_INTERNET_UNAVAILABLE` | A read reached the server phase with no connection (fires after the cached page on a two-phase fetch). | +| `ERROR_OFFLINE_SYNC_IN_PROGRESS` | `forceSyncNow(callback)` was called while a sync is already running. | +| `ERROR_OFFLINE_SYNC_COOLDOWN` | `forceSyncNow(callback)` was called within the throttle cooldown. | +| `ERROR_OFFLINE_DISABLED` | An offline-only operation was called while offline support is not active. | +| `ERROR_OFFLINE_MESSAGE_DISCARDED` | A queued message was removed before it could send. | +| `ERROR_OFFLINE_MESSAGE_IN_FLIGHT` | `retryMessage` / `discardLocalMessage` targeted a message that is still being sent. | +| `ERROR_OFFLINE_MESSAGE_NOT_FOUND` | `retryMessage` / `discardLocalMessage` found no unsent message with the given local id. | +| `ERROR_OFFLINE_MESSAGE_PERSIST_FAILED` | The local cache was briefly unavailable, so `getPendingMessages`, `retryMessage`, `retryAllFailedMessages` or `discardLocalMessage` changed nothing. Try again. | +| `ERROR_PENDING_QUEUE_LIMIT` | A send was refused because the outbox is full or the media is too large; the reason is `QUEUE_FULL` or `BYTE_OVERFLOW`. | + +## Deprecated overloads and replacements + +Starting in **5.1.0** the legacy single-callback (`CallbackListener`) fetch, single-item, and send overloads are deprecated in favor of the two-phase listeners below, which also deliver the cached result when offline support is on. The old overloads keep working: with offline off they behave exactly as before (the server result arrives in the `onServer…` / `onMessageSent` callback instead of `onSuccess`). + +| Deprecated overload | Replacement | +|---|---| +| `MessagesRequest.fetchNext(CallbackListener>)` | `MessagesRequest.fetchNext(MessageFetchListener)` | +| `MessagesRequest.fetchPrevious(CallbackListener>)` | `MessagesRequest.fetchPrevious(MessageFetchListener)` | +| `ThreadsRequest.fetchNext(CallbackListener>)` | `ThreadsRequest.fetchNext(ThreadsFetchListener)` | +| `CometChat.getMessageDetails(long, CallbackListener)` | `CometChat.getMessageDetails(long, MessageDetailsListener)` | +| `ConversationsRequest.fetchNext(CallbackListener>)` | `ConversationsRequest.fetchNext(ConversationFetchListener)` | +| `CometChat.getConversation(..., CallbackListener)` | `CometChat.getConversation(..., ConversationDetailsListener)` | +| `UsersRequest.fetchNext(CallbackListener>)` | `UsersRequest.fetchNext(UserFetchListener)` | +| `BlockedUsersRequest.fetchNext(CallbackListener>)` | `BlockedUsersRequest.fetchNext(UserFetchListener)` | +| `GroupsRequest.fetchNext(CallbackListener>)` | `GroupsRequest.fetchNext(GroupFetchListener)` | +| `GroupMembersRequest.fetchNext(CallbackListener>)` | `GroupMembersRequest.fetchNext(GroupMemberFetchListener)` | +| `BannedGroupMembersRequest.fetchNext(CallbackListener>)` | `BannedGroupMembersRequest.fetchNext(BannedGroupMemberFetchListener)` | +| `ReactionsRequest.fetchNext(CallbackListener>)` | `ReactionsRequest.fetchNext(ReactionFetchListener)` | +| `ReactionsRequest.fetchPrevious(CallbackListener>)` | `ReactionsRequest.fetchPrevious(ReactionFetchListener)` | +| `NotificationFeedRequest.fetchNext(CallbackListener>)` | `NotificationFeedRequest.fetchNext(NotificationFeedFetchListener)` | +| `CometChat.sendMessage(TextMessage, CallbackListener)` | `CometChat.sendMessage(TextMessage, MessageSentListener)` | +| `CometChat.sendMediaMessage(MediaMessage, CallbackListener)` | `CometChat.sendMediaMessage(MediaMessage, MessageSentListener)` | +| `CometChat.sendCustomMessage(CustomMessage, CallbackListener)` | `CometChat.sendCustomMessage(CustomMessage, MessageSentListener)` | +| `CometChat.sendInteractiveMessage(InteractiveMessage, CallbackListener)` | `CometChat.sendInteractiveMessage(InteractiveMessage, MessageSentListener)` | diff --git a/sdk/android/v5/offline-send.mdx b/sdk/android/v5/offline-send.mdx new file mode 100644 index 000000000..21d0b2902 --- /dev/null +++ b/sdk/android/v5/offline-send.mdx @@ -0,0 +1,410 @@ +--- +title: "Send While Offline" +sidebarTitle: "Send While Offline" +description: "Queue messages sent without a connection, track their status, and retry failed sends with the CometChat Android SDK." +--- + +import OfflineDeprecatedSend from '/snippets/sdk/offline-deprecated-send.mdx'; + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// Send with the offline-aware listener (queued when offline, auto-sent on reconnect) +CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { /* status == PENDING, fires < 100ms */ } + override fun onMessageSent(message: BaseMessage?) { /* server ack: real id + sentAt */ } + override fun onError(e: CometChatException?) { /* PENDING = queued, FAILED = failed */ } +}) + +// Read offline state off a message +val localId = message.localMessageId // stable client handle while offline is running +val status = message.status // PENDING, SENDING, SENT, DELIVERED, READ, FAILED +val reason = message.failureReason // MessageFailureReason, set only while FAILED + +// Inspect and recover the outbox +CometChat.getPendingMessages(callback) // PENDING + SENDING + FAILED +CometChat.retryMessage(localId, messageSentListener) // one message +CometChat.retryAllFailedMessages(messageSentListener) // transient failures only +CometChat.discardLocalMessage(localId, stringCallback) // delete an unsent message + +// Observe every outbox transition, app-wide +CometChat.addOfflineMessageListener("LISTENER_ID", object : CometChat.OfflineMessageListener() { + override fun onOutboxMessagePending(message: BaseMessage?) {} + override fun onOutboxMessageSent(message: BaseMessage?) {} + override fun onOutboxMessageFailed(message: BaseMessage?, reason: MessageFailureReason?) {} +}) +``` + + + +With offline support enabled, a message you send while the device has no connection is **durably queued on the device** instead of failing. It keeps its place across an app restart and is sent automatically when the connection returns. This page shows the offline-aware send listener, how to read a message's status, how to inspect and recover the send queue, and the queue limits. + + + +Offline support is **off by default** and requires SDK **5.1.0** or newer. Queuing only happens once you [enable it](/sdk/android/v5/offline-enable); with offline off, a send over no connection fails exactly as before. + + + + + +## Send a Message + +Pass a `MessageSentListener` to `sendMessage`. Its lifecycle is `onMessagePending` first (the message is durably queued), then exactly one final outcome — `onMessageSent` (the server accepted it) or, for a real failure, `onError`. + + + +```java +TextMessage textMessage = new TextMessage("UID", "Hello!", CometChatConstants.RECEIVER_TYPE_USER); + +CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { + @Override + public void onMessagePending(BaseMessage message) { + // Queued and persisted; message.getStatus() == MessageStatus.PENDING + Log.d(TAG, "Pending, local id: " + message.getLocalMessageId()); + } + + @Override + public void onMessageSent(BaseMessage message) { + // Server ack: message now carries its real id and sentAt + Log.d(TAG, "Sent: " + message.getId()); + } + + @Override + public void onError(CometChatException e) { + // Inspect the message status to tell "queued, not failed" from a real failure + Log.e(TAG, "Send error: " + e.getCode()); + } +}); +``` + + + + +```kotlin +val textMessage = TextMessage("UID", "Hello!", CometChatConstants.RECEIVER_TYPE_USER) + +CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Queued and persisted; message?.status == MessageStatus.PENDING + Log.d(TAG, "Pending, local id: ${message?.localMessageId}") + } + + override fun onMessageSent(message: BaseMessage?) { + // Server ack: message now carries its real id and sentAt + Log.d(TAG, "Sent: ${message?.id}") + } + + override fun onError(e: CometChatException?) { + // Inspect the message status to tell "queued, not failed" from a real failure + Log.e(TAG, "Send error: ${e?.code}") + } +}) +``` + + + + + +The same `MessageSentListener` is accepted by all four persistent send methods: + + + +```java +CometChat.sendMessage(textMessage, listener); // TextMessage +CometChat.sendMediaMessage(mediaMessage, listener); // MediaMessage +CometChat.sendCustomMessage(customMessage, listener); // CustomMessage +CometChat.sendInteractiveMessage(interactiveMessage, listener); // InteractiveMessage +``` + + + + +```kotlin +CometChat.sendMessage(textMessage, listener) // TextMessage +CometChat.sendMediaMessage(mediaMessage, listener) // MediaMessage +CometChat.sendCustomMessage(customMessage, listener) // CustomMessage +CometChat.sendInteractiveMessage(interactiveMessage, listener) // InteractiveMessage +``` + + + + + +## Queued, Not Failed + +When the device is offline, the send still fires `onMessagePending`, then fires `onError` **once** with the code `ERROR_INTERNET_UNAVAILABLE`. This is a **notification, not a failure**: the message stays durably `PENDING`, and `onMessageSent` fires **later**, on reconnect, when it actually goes. + +So for a single send, `onError` and `onMessageSent` can **both** fire (offline, then reconnect), and `onError` does not always mean the message failed. Always read `message.getStatus()` to tell the two apart: + +| `status` after `onError` | Meaning | What to do | +|---|---|---| +| `PENDING` | Queued; not sent yet. Will auto-send on reconnect. | Show as sending/queued; nothing to do. | +| `FAILED` | A real failure; will not retry on its own. | Offer the user a manual retry (see below). | + + + +```java +@Override +public void onError(CometChatException e) { + if (message.getStatus() == MessageStatus.PENDING) { + // Queued offline — it will send automatically on reconnect + } else if (message.getStatus() == MessageStatus.FAILED) { + // Real failure — surface a retry action + } +} +``` + + + + +```kotlin +override fun onError(e: CometChatException?) { + when (message.status) { + MessageStatus.PENDING -> { /* Queued offline — auto-sends on reconnect */ } + MessageStatus.FAILED -> { /* Real failure — surface a retry action */ } + else -> {} + } +} +``` + + + + + +### Message status and failure reason + +Every message exposes its offline state. `getLocalMessageId()` returns the SDK-generated handle that is stamped at compose (while offline is running) and stays stable across the whole lifecycle — use it to retry or discard the message. + +| Accessor | Type | Notes | +|---|---|---| +| `getLocalMessageId()` | `String` | Client handle; present while offline is running. | +| `getStatus()` | `MessageStatus` | `PENDING`, `SENDING`, `SENT`, `DELIVERED`, `READ`, `FAILED`. | +| `getFailureReason()` | `MessageFailureReason` | Set only while `FAILED`; otherwise empty. | + +`MessageFailureReason` values: + +| Reason | When | +|---|---| +| `TRANSIENT` | A temporary server error (for example 5xx or rate-limited), or no answer after the auto-retry cap. Retryable. | +| `PERMANENT` | The server rejected the message, or the error was not recognized. A manual retry will not help. | +| `QUEUE_FULL` | A new send was refused because the queue is full (count cap). | +| `QUEUE_OVERFLOW` | An older queued message was evicted to admit a newer one (`DROP_OLDEST` only). | +| `BYTE_OVERFLOW` | A media send was refused because it exceeds the queue's byte budget. | +| `DISCARDED` | The message was removed before it could send (for example `discardLocalMessage`, or a cache clear). | + +## Inspect the Queue + +`getPendingMessages` returns the **entire un-acked outbox** — every `PENDING`, `SENDING`, and `FAILED` message, ordered by local compose time. Sent messages are not included. Read each message's `status` to group them. + + + +```java +CometChat.getPendingMessages(new CometChat.CallbackListener>() { + @Override + public void onSuccess(List messages) { + for (BaseMessage m : messages) { + Log.d(TAG, m.getLocalMessageId() + " -> " + m.getStatus()); + } + } + + @Override + public void onError(CometChatException e) { + Log.e(TAG, "Could not read the outbox: " + e.getCode()); + } +}); +``` + + + + +```kotlin +CometChat.getPendingMessages(object : CometChat.CallbackListener>() { + override fun onSuccess(messages: List) { + messages.forEach { Log.d(TAG, "${it.localMessageId} -> ${it.status}") } + } + + override fun onError(e: CometChatException?) { + Log.e(TAG, "Could not read the outbox: ${e?.code}") + } +}) +``` + + + + + +### Observe the queue app-wide + +The per-call `MessageSentListener` lives only as long as the call. To watch every queue transition — a live send, a reconnect auto-flush, a manual retry, or a resumed send after an app restart — register a global `OfflineMessageListener`. Its failed callback carries the `MessageFailureReason`. + + + +```java +CometChat.addOfflineMessageListener("LISTENER_ID", new CometChat.OfflineMessageListener() { + @Override + public void onOutboxMessagePending(BaseMessage message) { } + + @Override + public void onOutboxMessageSent(BaseMessage message) { } + + @Override + public void onOutboxMessageFailed(BaseMessage message, MessageFailureReason reason) { + Log.e(TAG, "Failed: " + message.getLocalMessageId() + " (" + reason + ")"); + } +}); + +// Remove it when no longer needed +CometChat.removeOfflineMessageListener("LISTENER_ID"); +``` + + + + +```kotlin +CometChat.addOfflineMessageListener("LISTENER_ID", object : CometChat.OfflineMessageListener() { + override fun onOutboxMessagePending(message: BaseMessage?) {} + override fun onOutboxMessageSent(message: BaseMessage?) {} + override fun onOutboxMessageFailed(message: BaseMessage?, reason: MessageFailureReason?) { + Log.e(TAG, "Failed: ${message?.localMessageId} ($reason)") + } +}) + +// Remove it when no longer needed +CometChat.removeOfflineMessageListener("LISTENER_ID") +``` + + + + + +## Retry and Discard + +There is no automatic retry of a `FAILED` message — recovery is explicit and user-invoked. Use the message's `localMessageId`. + +- **`retryMessage(localMessageId, listener)`** re-attempts one message. A `FAILED` message is reset to `PENDING` and sent; a `PENDING` message is attempted now rather than waiting for a reconnect. The listener receives that attempt's outcome. +- **`retryAllFailedMessages(listener)`** retries **transient** failures only; permanent ones are skipped. +- **`discardLocalMessage(localMessageId, listener)`** deletes an unsent (`PENDING` or `FAILED`) message from the device, including its copied media. `onSuccess` receives the `localMessageId`. + + + +```java +CometChat.retryMessage(localMessageId, messageSentListener); + +CometChat.retryAllFailedMessages(messageSentListener); + +CometChat.discardLocalMessage(localMessageId, new CometChat.CallbackListener() { + @Override + public void onSuccess(String discardedLocalId) { } + + @Override + public void onError(CometChatException e) { + Log.e(TAG, "Discard rejected: " + e.getCode()); + } +}); +``` + + + + +```kotlin +CometChat.retryMessage(localMessageId, messageSentListener) + +CometChat.retryAllFailedMessages(messageSentListener) + +CometChat.discardLocalMessage(localMessageId, object : CometChat.CallbackListener() { + override fun onSuccess(discardedLocalId: String?) {} + override fun onError(e: CometChatException?) { + Log.e(TAG, "Discard rejected: ${e?.code}") + } +}) +``` + + + + + +`retryMessage` and `discardLocalMessage` reject, changing nothing, when: + +| Error code | Reason | +|---|---| +| `ERROR_OFFLINE_MESSAGE_IN_FLIGHT` | The message is already sending (`SENDING`). | +| `ERROR_OFFLINE_MESSAGE_NOT_FOUND` | No unsent message has that id (unknown, or already sent). | +| `ERROR_OFFLINE_DISABLED` | Offline support is not running. | +| `ERROR_OFFLINE_MESSAGE_PERSIST_FAILED` | The local cache was briefly unavailable. Try again. | + +## Auto-send on Reconnect + +By default, queued `PENDING` messages are sent automatically when the realtime connection is re-established. This is controlled by `OfflineConfig.autoSendPendingOnReconnect` (default `true`); see [Enable Offline Support](/sdk/android/v5/offline-enable) and the [reference](/sdk/android/v5/offline-reference). With it off, nothing retries a queued message on reconnect — a send that gets no answer becomes `FAILED` (`TRANSIENT`), which you recover with `retryMessage`. + +Reconnect also runs a catch-up sync for received messages — see [Stay in Sync](/sdk/android/v5/offline-sync). + +## Queue Limits + +The outbox holds at most `maxPendingMessages` messages still trying to send (`PENDING` + `SENDING`); `FAILED` messages do not count. When the queue is full, `OfflineConfig.pendingQueueOverflowPolicy` decides what happens: + +| `PendingMessageOverflowPolicy` | Behaviour on overflow | +|---|---| +| `REJECT_NEWEST` (**default**) | The new send is refused and reported with reason `QUEUE_FULL`. Nothing already queued is lost. | +| `DROP_OLDEST` | The oldest `PENDING` message is evicted (reason `QUEUE_OVERFLOW`) to admit the new one. | + +A media send that exceeds the queue's byte budget is refused with reason `BYTE_OVERFLOW`. + +A **refused** send (`QUEUE_FULL` / `BYTE_OVERFLOW`) is never queued, so it fires **no** `onMessagePending`; it reports `onError` with the code `ERROR_PENDING_QUEUE_LIMIT`, and the global `OfflineMessageListener` receives `onOutboxMessageFailed(message, reason)` carrying the exact reason. + + + +A refused or evicted message (`QUEUE_FULL`, `QUEUE_OVERFLOW`, `BYTE_OVERFLOW`) has **no durable row** — it does not appear in `getPendingMessages`, and calling `retryMessage` on its id does nothing. Capture these from the failure callback and handle them at that moment. Prefer the default `REJECT_NEWEST`, which never silently drops an already-queued message from another conversation. + + + +## Edits, Deletes, and Reactions Need a Connection + +Offline queuing applies to **sending new messages** only. Changes to existing messages — edit, delete, add or remove a reaction — and other own actions are **not queued offline**. They are sent to the server whenever you trigger them, and they report the not-connected error if the request gets no answer. Their effect reaches the cache only once the server accepts them. + +## Durable Media Uploads + +A multi-attachment upload survives an app restart: the SDK re-attaches to an in-progress batch instead of restarting it. Fetch the existing request for a conversation with `getExistingUploadFileRequest`, and read each file's progress through `getItems()`, which returns a list of `UploadFileItemStatus` (each with a `State`: `QUEUED`, `UPLOADING`, `UPLOADED`, `FAILED`, or `REJECTED`, which cannot be retried). Retry an individual file with `retryAttachment(fileId)`. + + + +```java +UploadFileRequest request = CometChat.getExistingUploadFileRequest("UID", + CometChatConstants.RECEIVER_TYPE_USER); + +if (request != null) { + for (UploadFileItemStatus item : request.getItems()) { + Log.d(TAG, item.getFileId() + " -> " + item.getState()); + if (item.getState() == UploadFileItemStatus.State.FAILED) { + request.retryAttachment(item.getFileId()); + } + } +} +``` + + + + +```kotlin +val request = CometChat.getExistingUploadFileRequest("UID", + CometChatConstants.RECEIVER_TYPE_USER) + +request?.items?.forEach { item -> + Log.d(TAG, "${item.fileId} -> ${item.state}") + if (item.state == UploadFileItemStatus.State.FAILED) { + request.retryAttachment(item.fileId) + } +} +``` + + + + + +See [Media Caching](/sdk/android/v5/offline-media-caching) for downloading and caching received attachments. + + + +For the full list of offline methods, listeners, enums, model fields, and error codes, see the [Offline Configuration & API Reference](/sdk/android/v5/offline-reference). + + diff --git a/sdk/android/v5/offline-sync.mdx b/sdk/android/v5/offline-sync.mdx new file mode 100644 index 000000000..460d61d9a --- /dev/null +++ b/sdk/android/v5/offline-sync.mdx @@ -0,0 +1,331 @@ +--- +title: "Stay in Sync" +sidebarTitle: "Stay in Sync" +description: "Catch up on everything that changed while offline, observe sync progress, trigger a manual sync, and manage the local cache with the CometChat Android SDK." +--- + +{/* TL;DR for Agents and Quick Reference */} + + +```kotlin +// Observe the reconnect catch-up sync (conversation phase, then message phase) +CometChat.addSyncListener("SYNC_LISTENER", object : CometChat.SyncListener() { + override fun onConversationSyncCompleted(fetched: List) { /* blind-replace list */ } + override fun onMessageSyncCompleted(newPlusUpdates: List, outcome: SyncOutcome) { } + override fun onMessageSyncError(error: CometChatException?) { } +}) +CometChat.removeSyncListener("SYNC_LISTENER") + +// Poll sync state +val state: SyncState = CometChat.getSyncState() // IDLE | SYNCING +val lastSync: Long = CometChat.getLastSyncTimestamp() // server epoch seconds, 0 if never + +// Trigger a manual catch-up (with an acceptance callback) +CometChat.forceSyncNow(object : CometChat.CallbackListener() { + override fun onSuccess(p: String?) { } + override fun onError(e: CometChatException?) { /* IN_PROGRESS / COOLDOWN / DISABLED / NOT_LOGGED_IN */ } +}) + +// Observe cache rebuilds +CometChat.addOfflineCacheResetListener("RESET_LISTENER", object : OfflineCacheResetListener() { + override fun onOfflineCacheReset(reason: OfflineCacheResetReason, pendingMessagesLost: Int) { } +}) + +// Inspect / clear the cache +val bytes: Long = CometChat.getCacheSize() +CometChat.clearOfflineCache() // destructive; drops the un-sent outbox +``` + + + +When the device comes back online, the SDK runs a **catch-up sync**: it refreshes the conversation list, then replays everything that changed in your messages since the last sync, and merges the results into the local cache. This page covers how to observe that sync, trigger one on demand, and manage the cache it maintains. + + + +Everything on this page is a no-op unless offline support is active for the logged-in user (`CometChat.isOfflineEnabled()` is `true`). See [Enable Offline Support](/sdk/android/v5/offline-enable). + + + +## How catch-up works + +A sync runs automatically when the connection settles after a reconnect. It never runs on a brief connect-then-drop flap, and overlapping triggers coalesce into a single sweep. It proceeds in two phases, in order: + +1. **Conversation phase** — the conversation list is fetched as one fresh page. A conversation-list UI can blind-replace its list with the result. +2. **Message phase** — the SDK replays the message changelog from the last sync point. New messages and updates to already-cached messages are merged into the cache. + +The sync is incremental and safe to repeat: it only moves forward from the last successful point, it never deletes cached messages, and a failed or interrupted phase simply resumes on the next reconnect. A first-ever sync (cold start) seeds its starting point from the newest conversation and fetches no message history — older history is reached by scrolling online. + +## Listen for sync progress + +Register a `CometChat.SyncListener` to observe the lifecycle of every automatic and manual sync. Each phase fires `started`, then exactly one of `completed` / `error`, all on the main thread. + + + +```java +CometChat.addSyncListener("SYNC_LISTENER", new CometChat.SyncListener() { + @Override + public void onConversationSyncStarted() { } + + @Override + public void onConversationSyncCompleted(List fetched) { + // The full, fresh conversation page. Blind-replace your list. + } + + @Override + public void onConversationSyncError(CometChatException error) { } + + @Override + public void onMessageSyncStarted() { } + + @Override + public void onMessageSyncCompleted(List newPlusUpdates, SyncOutcome outcome) { + // New + changed messages reconciled this sync. Merge by message id. + } + + @Override + public void onMessageSyncError(CometChatException error) { } +}); +``` + + + + +```kotlin +CometChat.addSyncListener("SYNC_LISTENER", object : CometChat.SyncListener() { + override fun onConversationSyncStarted() {} + + override fun onConversationSyncCompleted(fetched: List) { + // The full, fresh conversation page. Blind-replace your list. + } + + override fun onConversationSyncError(error: CometChatException?) {} + + override fun onMessageSyncStarted() {} + + override fun onMessageSyncCompleted(newPlusUpdates: List, outcome: SyncOutcome) { + // New + changed messages reconciled this sync. Merge by message id. + } + + override fun onMessageSyncError(error: CometChatException?) {} +}) +``` + + + + +| Callback | When it fires | +|---|---| +| `onConversationSyncStarted()` | The conversation refresh began. | +| `onConversationSyncCompleted(List fetched)` | The refresh succeeded; `fetched` is the full conversation page. | +| `onConversationSyncError(CometChatException)` | A transport or parse failure; the cache is left untouched and the refresh retries on the next reconnect. | +| `onMessageSyncStarted()` | The message changelog sweep began. Never fires on a cold start. | +| `onMessageSyncCompleted(List newPlusUpdates, SyncOutcome)` | The sweep finished; `newPlusUpdates` carries the new and changed messages. | +| `onMessageSyncError(CometChatException)` | The sweep failed; it resumes on the next reconnect. | + +The two phases are independent — a conversation-phase error does not stop the message phase. A phase error is informational: the cache is never left in a wrong state and the sweep resumes automatically. + +The `SyncListener` is self-sufficient: `onMessageSyncCompleted` carries the swept messages, including `action` and `call` messages that the `MessageListener` does not deliver, so you can use it without a `MessageListener`. + +`onMessageSyncCompleted` reports the outcome: + +| `SyncOutcome` | Meaning | +|---|---| +| `CAUGHT_UP` | The changelog was fully replayed; the cache reflects every server change up to the sync point. | +| `PARTIAL` | A page was ingested and the sync point advanced, but a backlog remains; it drains one page per reconnect. | + +Remove the listener with `CometChat.removeSyncListener("SYNC_LISTENER")`. + +## Receiving synced messages + +If you already use a `CometChat.MessageListener`, synced messages still arrive there, so you usually do not need a `SyncListener` to keep your chat up to date: + +- **Genuinely new** messages arrive one-by-one on `onMessageReceived` (and the typed variants), tagged with source `SYNC`. +- **Updates** to already-cached messages arrive together on `onMessagesSynced(List)`. + + + +```java +CometChat.addMessageListener("MSG_LISTENER", new CometChat.MessageListener() { + @Override + public void onMessageReceived(BaseMessage message) { + if (MessageSource.SYNC == message.getSource()) { + // Delivered by a catch-up sync. Suppress notification spam if you wish. + } + } + + @Override + public void onMessagesSynced(List messages) { + // Updates to already-cached messages. Merge by id. + } +}); +``` + + + + +```kotlin +CometChat.addMessageListener("MSG_LISTENER", object : CometChat.MessageListener() { + override fun onMessageReceived(message: BaseMessage) { + if (MessageSource.SYNC == message.source) { + // Delivered by a catch-up sync. Suppress notification spam if you wish. + } + } + + override fun onMessagesSynced(messages: List) { + // Updates to already-cached messages. Merge by id. + } +}) +``` + + + + +## Check sync state + +Poll the current sync state and the time of the last completed sync at any time. Both are safe to call on the main thread. + + + +```java +SyncState state = CometChat.getSyncState(); // IDLE or SYNCING +long lastSync = CometChat.getLastSyncTimestamp(); // server epoch seconds; 0 if never synced +``` + + + + +```kotlin +val state: SyncState = CometChat.getSyncState() // IDLE or SYNCING +val lastSync: Long = CometChat.getLastSyncTimestamp() // server epoch seconds; 0 if never synced +``` + + + + +`getSyncState()` returns `SyncState.SYNCING` while a sweep is in flight, otherwise `SyncState.IDLE` (also when offline support is disabled). `getLastSyncTimestamp()` returns a **server-provided** epoch value in seconds, never the device clock, and `0` if the cache has never synced. + +## Trigger a manual sync + +Call `forceSyncNow` to run a catch-up immediately — for example behind a pull-to-refresh. It is fire-and-forget and routes through the same throttle as the automatic sync, so it will not start a second overlapping sweep or hammer the backend. + +Use the callback overload to learn whether the request was accepted; the sync's actual progress and outcome still arrive on your registered `SyncListener`. + + + +```java +CometChat.forceSyncNow(new CometChat.CallbackListener() { + @Override + public void onSuccess(String result) { + // Accepted and started. + } + + @Override + public void onError(CometChatException e) { + // Rejected — see the code. + } +}); +``` + + + + +```kotlin +CometChat.forceSyncNow(object : CometChat.CallbackListener() { + override fun onSuccess(result: String?) { + // Accepted and started. + } + + override fun onError(e: CometChatException?) { + // Rejected — see the code. + } +}) +``` + + + + +If you do not need the acceptance result, call the no-argument `CometChat.forceSyncNow()`; a rejected request is silently dropped. + +| Rejection code (`CometChatConstants.Errors`) | Meaning | +|---|---| +| `ERROR_OFFLINE_SYNC_IN_PROGRESS` | A sync (automatic or manual) is already running. | +| `ERROR_OFFLINE_SYNC_COOLDOWN` | A sync ran too recently; the error message carries the remaining cooldown in milliseconds (retry-after). | +| `ERROR_OFFLINE_DISABLED` | A user is logged in but offline support is not active. | +| `ERROR_USER_NOT_LOGGED_IN` | Nobody is logged in. | + +## When the cache resets + +In rare cases the SDK cannot keep the existing cache — for example the encryption key became unrecoverable, or the database file is corrupt. When this happens it rebuilds the cache from scratch and notifies a `OfflineCacheResetListener`. Any un-sent messages that were queued are lost in the rebuild; `pendingMessagesLost` reports how many (it may be unknown). + + + +```java +CometChat.addOfflineCacheResetListener("RESET_LISTENER", new OfflineCacheResetListener() { + @Override + public void onOfflineCacheReset(OfflineCacheResetReason reason, int pendingMessagesLost) { + // The cache was rebuilt. Re-fetch anything you need; it will re-sync from the server. + } +}); +``` + + + + +```kotlin +CometChat.addOfflineCacheResetListener("RESET_LISTENER", object : OfflineCacheResetListener() { + override fun onOfflineCacheReset(reason: OfflineCacheResetReason, pendingMessagesLost: Int) { + // The cache was rebuilt. Re-fetch anything you need; it will re-sync from the server. + } +}) +``` + + + + +| `OfflineCacheResetReason` | Cause | +|---|---| +| `KEY_LOST` | The device key could not be recovered (OS update, cleared app data, restored backup); a fresh key was minted. | +| `WRONG_KEY` | The database exists but does not decrypt under the current key. | +| `CORRUPTED` | The database file is corrupt on disk. | +| `ESCALATED` | An unclassified failure recurred across launches and was escalated to a permanent rebuild. | +| `ENCRYPTION_CHANGED` | The `encryption` setting changed, so the on-disk cache was rebuilt in the configured format. | + +Remove the listener with `CometChat.removeOfflineCacheResetListener("RESET_LISTENER")`. See [Encrypt the Cache](/sdk/android/v5/offline-encryption) for the encryption-related reasons. + +## Manage the cache + +Read the current cache size, or clear the cache entirely. + + + +```java +long bytes = CometChat.getCacheSize(); // DB + media byte store, in bytes + +CometChat.clearOfflineCache(); // wipe the entire per-user cache +``` + + + + +```kotlin +val bytes: Long = CometChat.getCacheSize() // DB + media byte store, in bytes + +CometChat.clearOfflineCache() // wipe the entire per-user cache +``` + + + + +`getCacheSize()` returns the total cache size in bytes (local database plus the media byte store) as a single figure, or `0` when offline support is disabled or nobody is logged in. + + + +`clearOfflineCache()` is destructive and irreversible. It empties the entire per-user cache — cached messages, conversations, media bytes **and the un-sent outbox**. Each still-queued `PENDING` / `SENDING` message fails with reason `DISCARDED`. It is a no-op unless offline support is active. + + + + + +You never need to clear the cache on logout — the SDK does it for you. The cache is stored **per user**: it is kept across re-login as the same user, and wiped automatically on logout, token expiry, or when a different user logs in. See [Offline Support](/sdk/android/v5/offline-overview). + + diff --git a/snippets/sdk/offline-deprecated-listener.mdx b/snippets/sdk/offline-deprecated-listener.mdx new file mode 100644 index 000000000..7c496516b --- /dev/null +++ b/snippets/sdk/offline-deprecated-listener.mdx @@ -0,0 +1,3 @@ + +**Starting with v5.1.0, the `CallbackListener` overload of {method} is deprecated.** Use {replacement} instead. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the server result arrives in the `onServer…` callback instead of `onSuccess`. With offline support on, it also delivers the cached result first. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + diff --git a/snippets/sdk/offline-deprecated-send.mdx b/snippets/sdk/offline-deprecated-send.mdx new file mode 100644 index 000000000..bfc8893da --- /dev/null +++ b/snippets/sdk/offline-deprecated-send.mdx @@ -0,0 +1,3 @@ + +**Starting with v5.1.0, the `CallbackListener` overloads of {method} are deprecated.** Use `MessageSentListener` instead. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the sent message arrives in `onMessageSent` instead of `onSuccess`. With offline support on, `onMessagePending` fires first, and a message sent without a connection is queued and sent when the connection returns. See [Send While Offline](/sdk/android/v5/offline-send). + From 12d6c95838768cc4ba2f17354754a5e1a3c32fac Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 28 Oct 2026 18:45:21 +0530 Subject: [PATCH 02/42] docs(android): use the 5.1.0 offline listeners across the v5 SDK pages Swaps the fetch, single-item and send samples on 24 Android v5 pages to the two-phase fetch listeners and MessageSentListener, adds the 5.1.0 deprecation callout, an Offline Behaviour section with the cached vs server-only filters for each builder, and short offline notes on setup, auth, listeners, edit, delete, group and connection pages. Fixes the ReactionsRequest class name on the reactions page. Co-Authored-By: Claude Opus 5.5 --- .../v5/additional-message-filtering.mdx | 37 +++ sdk/android/v5/ai-moderation.mdx | 28 +- sdk/android/v5/authentication-overview.mdx | 4 + sdk/android/v5/block-users.mdx | 43 ++- sdk/android/v5/campaigns.mdx | 45 ++- sdk/android/v5/connection-status.mdx | 4 + sdk/android/v5/delete-conversation.mdx | 4 + sdk/android/v5/delete-group.mdx | 4 + sdk/android/v5/delete-message.mdx | 4 + sdk/android/v5/edit-message.mdx | 4 + sdk/android/v5/group-kick-member.mdx | 38 ++- sdk/android/v5/leave-group.mdx | 4 + sdk/android/v5/mentions.mdx | 128 ++++++-- sdk/android/v5/pin-conversation.mdx | 41 ++- sdk/android/v5/pin-message.mdx | 66 +++-- sdk/android/v5/reactions.mdx | 96 +++--- sdk/android/v5/real-time-listeners.mdx | 4 + sdk/android/v5/receive-messages.mdx | 159 ++++++++-- sdk/android/v5/retrieve-conversations.mdx | 73 +++-- sdk/android/v5/retrieve-group-members.mdx | 40 ++- sdk/android/v5/retrieve-groups.mdx | 42 ++- sdk/android/v5/retrieve-users.mdx | 50 +++- sdk/android/v5/save-message.mdx | 41 ++- sdk/android/v5/send-message.mdx | 276 ++++++++++++++---- sdk/android/v5/setup.mdx | 4 + sdk/android/v5/thread-subscription.mdx | 38 ++- sdk/android/v5/threaded-messages.mdx | 67 ++++- sdk/android/v5/upload-files.mdx | 25 +- 28 files changed, 1089 insertions(+), 280 deletions(-) diff --git a/sdk/android/v5/additional-message-filtering.mdx b/sdk/android/v5/additional-message-filtering.mdx index 8d6465d42..f61a487d4 100644 --- a/sdk/android/v5/additional-message-filtering.mdx +++ b/sdk/android/v5/additional-message-filtering.mdx @@ -464,6 +464,8 @@ This can be useful in finding the messages that have been received or updated af This can be achieved easily by setting the updatesOnly parameter to true. To do so, you can use the updatesOnly() method. This method takes a boolean input and can be used with the `setUpdatedAfter()` method to get jus the updated messages and not the messages sent/received after the specified time. This method cannot be used independently and always needs to be used with the `setUpdatedAfter()` method. +With offline support on, this filter is answered by the server only; the cached page is empty. + ```java @@ -1022,6 +1024,8 @@ In other words, as a logged-in user, how do I fetch messages that contains links In order to do this, you can use the `hasLinks()` method. This method accepts boolean as input. When set to `true` , the SDK will fetch messages which have links in the text. The default value for this parameter is `false`. +With offline support on, this filter is answered by the server only; the cached page is empty. + This feature is only available with `Conversation & Advanced Search`. The `Conversation & Advanced Search` is only available in `Advanced` & `Custom` [plans](https://www.cometchat.com/pricing). If you're already on one of these plans, please enable the `Conversation & Advanced Search` from [CometChat Dashboard](https://app.cometchat.com) (Open your app, navigate to Chats -> Settings -> General Configuration) @@ -1278,6 +1282,8 @@ val UID = "cometchat-uid-1" This can be achieved by setting the pinned flag to true using the `setPinned()` method. Setting a `UID` or a `GUID` is mandatory for this filter — pinned messages are always scoped to a single conversation. The returned list is sorted by the time of pinning, most recently pinned first. +With offline support on, the pinned list is served from the cache first, then from the server. See [Pin A Message](/sdk/android/v5/pin-message). + ```java @@ -1341,6 +1347,8 @@ For the full pin workflow — pinning, unpinning, limits and real-time events This can be achieved by setting the saved flag to true using the `setSaved()` method. Saved messages are private to the logged-in user and span all of their conversations, so this filter must **not** be combined with `setUID()` or `setGUID()`. The returned list is sorted by the time of saving, most recently saved first. +With offline support on, the saved list is served from the cache first, then from the server. See [Save A Message](/sdk/android/v5/save-message). + ```java @@ -1365,3 +1373,32 @@ val messagesRequest = MessagesRequestBuilder() For the full save workflow — saving, unsaving, limits and real-time events — see [Save A Message](/sdk/android/v5/save-message). + +## Offline Behaviour + +With offline support enabled, `fetchPrevious`/`fetchNext` answer from the on-device cache first (`onCachedMessages`, even when empty) and then from the server (`onServerMessages`). The cache answers a page **only when every filter on the request is locally computable** — a single server-only filter makes the cached page empty with no error, while the server phase still answers in full. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline behaviour | +| --- | --- | +| `setUnread(boolean)` | Cached | +| `hideMessagesFromBlockedUsers(boolean)` | Cached | +| `setUpdatedAfter(long)` | Cached | +| `updatesOnly(boolean)` | Server only — cached page is empty | +| `setCategories(List)` | Cached | +| `setTypes(List)` | Cached | +| `setParentMessageId(int)` | Cached | +| `hideReplies(boolean)` | Cached | +| `hideDeletedMessages(boolean)` | Cached | +| `hideQuotedMessages(boolean)` | Cached | +| `setTags(List)` | Cached | +| `withTags(boolean)` | Cached | +| `hasLinks(boolean)` | Server only — cached page is empty | +| `hasAttachments(boolean)` | Cached | +| `hasReactions(boolean)` | Cached | +| `setAttachmentTypes(List)` | Cached | +| `hasMentions(boolean)` | Cached | +| `setMentionedUIDs(List)` | Cached | +| `setPinned(boolean)` | Cached | +| `setSaved(boolean)` | Cached | + +`setMessageId`, `setTimestamp`, `setLimit`, `setUID` and `setGUID` are scope and pagination inputs, not filters, and do not change what the cache can answer. Setting both `setUID` and `setGUID` together on a non-thread read (one sender's messages in a group) is answered by the server only. diff --git a/sdk/android/v5/ai-moderation.mdx b/sdk/android/v5/ai-moderation.mdx index 748cde96b..26b2a89a3 100644 --- a/sdk/android/v5/ai-moderation.mdx +++ b/sdk/android/v5/ai-moderation.mdx @@ -3,6 +3,8 @@ title: "AI Moderation" description: "Screen text, image and video messages for unsafe content automatically in your Android app, and respond when each one is approved or rejected." --- +import OfflineDeprecatedSend from '/snippets/sdk/offline-deprecated-send.mdx'; + ## Overview AI Moderation in the CometChat SDK helps ensure that your chat application remains safe and compliant by automatically reviewing messages for inappropriate content. This feature leverages AI to moderate messages in real-time, reducing manual intervention and improving user experience. @@ -32,7 +34,7 @@ sequenceDiagram SDK->>CometChat: Send Message CometChat->>Moderation: Process Message CometChat-->>SDK: Message (status: PENDING) - SDK-->>App: onSuccess (PENDING) + SDK-->>App: onMessageSent (PENDING) Moderation-->>CometChat: Moderation Result CometChat-->>SDK: onMessageModerated SDK-->>App: APPROVED or DISAPPROVED @@ -73,15 +75,21 @@ The `getModerationStatus()` method returns one of the following values: When you send a text, image, or video message, check the initial moderation status: + + ```kotlin val textMessage = TextMessage(receiverUID, "Hello, how are you?", CometChatConstants.RECEIVER_TYPE_USER) - CometChat.sendMessage(textMessage, object : CometChat.CallbackListener() { - override fun onSuccess(message: TextMessage) { + CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(message: BaseMessage?) { // Check moderation status - if (message.moderationStatus == ModerationStatus.PENDING) { + if ((message as? TextMessage)?.moderationStatus == ModerationStatus.PENDING) { Log.d(TAG, "Message is under moderation review") // Show pending indicator in UI } @@ -97,11 +105,17 @@ When you send a text, image, or video message, check the initial moderation stat ```java TextMessage textMessage = new TextMessage(receiverUID, "Hello, how are you?", CometChatConstants.RECEIVER_TYPE_USER); - CometChat.sendMessage(textMessage, new CometChat.CallbackListener() { + CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { + @Override + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + @Override - public void onSuccess(TextMessage message) { + public void onMessageSent(BaseMessage message) { // Check moderation status - if (message.getModerationStatus().equals(ModerationStatus.PENDING)) { + if (message instanceof TextMessage + && ((TextMessage) message).getModerationStatus().equals(ModerationStatus.PENDING)) { Log.d(TAG, "Message is under moderation review"); // Show pending indicator in UI } diff --git a/sdk/android/v5/authentication-overview.mdx b/sdk/android/v5/authentication-overview.mdx index f99705049..dcba763e4 100644 --- a/sdk/android/v5/authentication-overview.mdx +++ b/sdk/android/v5/authentication-overview.mdx @@ -142,6 +142,10 @@ After the user logs in, their information is returned in the `User` object. You can use the `logout()` method to log out the user from CometChat. We suggest you call this method once your user has been successfully logged out from your app. + +With offline support enabled, logging out — or logging in as a different user — wipes the current user's on-device cache (its messages, media and queued messages). See [Stay in Sync](/sdk/android/v5/offline-sync). + + ```java diff --git a/sdk/android/v5/block-users.mdx b/sdk/android/v5/block-users.mdx index 5ab1606f8..41d307bf9 100644 --- a/sdk/android/v5/block-users.mdx +++ b/sdk/android/v5/block-users.mdx @@ -3,6 +3,8 @@ title: "Block Users" description: "Block or unblock users in your Android app so all messages to and from them stop, and fetch the list of everyone you have blocked." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + ## Block Users @@ -144,6 +146,8 @@ val blockedUsersRequest = BlockedUsersRequestBuilder() ### Set Search Keyword +With offline support on, this filter is answered by the server only; the cached page is empty. + This method allows you to set the search string based on which the blocked users are to be fetched. @@ -198,15 +202,26 @@ Finally, once all the parameters are set in the builder class, you need to call Once you have the object of the `BlockedUsersRequest` class, you need to call the `fetchNext()` method. Calling this method will return a list of `User` objects containing n number of blocked users where N is the limit set in the builder class. + + ```java BlockedUsersRequest blockedUsersRequest = new BlockedUsersRequest.BlockedUsersRequestBuilder().setLimit(10).build(); -blockedUsersRequest.fetchNext(new CometChat.CallbackListener>() { +blockedUsersRequest.fetchNext(new CometChat.UserFetchListener() { +@Override +public void onCachedUsers(List users) { + // Cached page: fires first (even when empty). Render immediately. + for (User user : users) { + Log.e(TAG, user.getUid()); + } +} + @Override -public void onSuccess(List users) { - for(User user : users){ +public void onServerUsers(List users) { + // Server page: reconcile your displayed list, keyed by uid. + for (User user : users) { Log.e(TAG, user.getUid()); } } @@ -224,8 +239,16 @@ public void onError(CometChatException e) { ```kotlin val blockedUsersRequest = BlockedUsersRequestBuilder().setLimit(10).build() -blockedUsersRequest.fetchNext(object : CallbackListener>() { -override fun onSuccess(users: List) { +blockedUsersRequest.fetchNext(object : CometChat.UserFetchListener() { +override fun onCachedUsers(users: List) { + // Cached page: fires first (even when empty). Render immediately. + for (user in users) { + Log.e(TAG, user.uid) + } +} + +override fun onServerUsers(users: List) { + // Server page: reconcile your displayed list, keyed by uid. for (user in users) { Log.e(TAG, user.uid) } @@ -240,3 +263,13 @@ override fun onError(e: CometChatException) { + +## Offline Behaviour + +With offline support enabled, `BlockedUsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the device (empty when nothing was recorded), then `onServerUsers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `setDirection` | Cached | +| `setSearchKeyword` | Server only — cached page is empty | diff --git a/sdk/android/v5/campaigns.mdx b/sdk/android/v5/campaigns.mdx index 57fc93c90..64c5182ca 100644 --- a/sdk/android/v5/campaigns.mdx +++ b/sdk/android/v5/campaigns.mdx @@ -3,6 +3,8 @@ title: "Campaigns" description: "CometChat Campaigns lets you deliver targeted, rich notifications to users via an in-app notification feed." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + CometChat Campaigns lets you deliver targeted, rich notifications to users via an in-app notification feed. Each notification is a **Card Schema JSON** — a structured layout rendered natively by the CometChat Cards library. The SDK provides APIs to fetch feed items, listen for real-time delivery, mark items as read/delivered, report engagement, and retrieve unread counts. @@ -278,12 +280,24 @@ val request = NotificationFeedRequest.NotificationFeedRequestBuilder() ### Fetch Items + + ```java -request.fetchNext(new CometChat.CallbackListener>() { +request.fetchNext(new CometChat.NotificationFeedFetchListener() { + @Override + public void onCachedNotificationFeedItems(List items) { + // Cached page: fires first (even when empty). Render immediately. + for (NotificationFeedItem item : items) { + String cardJson = item.getContent().toString(); + // Pass cardJson to CometChatCardView or CometChatCardComposable + } + } + @Override - public void onSuccess(List items) { + public void onServerNotificationFeedItems(List items) { + // Server page: reconcile your displayed list, keyed by item id. for (NotificationFeedItem item : items) { String cardJson = item.getContent().toString(); // Pass cardJson to CometChatCardView or CometChatCardComposable @@ -299,8 +313,17 @@ request.fetchNext(new CometChat.CallbackListener>() { ```kotlin -request.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(items: List) { +request.fetchNext(object : CometChat.NotificationFeedFetchListener() { + override fun onCachedNotificationFeedItems(items: List) { + // Cached page: fires first (even when empty). Render immediately. + items.forEach { item -> + val cardJson = item.content.toString() + // Pass cardJson to CometChatCardView or CometChatCardComposable + } + } + + override fun onServerNotificationFeedItems(items: List) { + // Server page: reconcile your displayed list, keyed by item id. items.forEach { item -> val cardJson = item.content.toString() // Pass cardJson to CometChatCardView or CometChatCardComposable @@ -317,6 +340,20 @@ request.fetchNext(object : CometChat.CallbackListener Call `fetchNext()` repeatedly for pagination. When the server has no more items, subsequent calls return an empty list. +## Offline Behaviour + +With offline support enabled, `NotificationFeedRequest.fetchNext()` on a `NotificationFeedFetchListener` first delivers `onCachedNotificationFeedItems` from the device (empty when nothing was recorded), then `onServerNotificationFeedItems` with the live page to reconcile by item `id`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `setReadState` | Cached | +| `setCategory` | Cached | +| `setChannelId` | Cached | +| `setTags` | Cached | +| `setDateFrom` | Cached | +| `setDateTo` | Cached | + ### NotificationFeedItem Fields | Field | Type | Description | diff --git a/sdk/android/v5/connection-status.mdx b/sdk/android/v5/connection-status.mdx index 34b2b384b..76fc89523 100644 --- a/sdk/android/v5/connection-status.mdx +++ b/sdk/android/v5/connection-status.mdx @@ -7,6 +7,10 @@ description: "Track whether your Android app is connecting, connected or disconn CometChat SDK provides you with a mechanism to get real-time status of the connection to CometChat web-socket servers. To achieve this you need to use the `ConnectionListener` class provided by the CometChat SDK + +With offline support enabled, your app keeps working without a connection: cached chats are shown instantly, and messages you send are queued and delivered automatically on reconnect. See [Show Cached Data](/sdk/android/v5/offline-cached-data) and [Send While Offline](/sdk/android/v5/offline-send). + + Connection Status provides you with the below 3 methods to get the status of the connection to CometChat web-socket servers: | Delegate Method | Information | diff --git a/sdk/android/v5/delete-conversation.mdx b/sdk/android/v5/delete-conversation.mdx index 126ecf8ae..8c21f29b5 100644 --- a/sdk/android/v5/delete-conversation.mdx +++ b/sdk/android/v5/delete-conversation.mdx @@ -9,6 +9,10 @@ In case you want to delete a conversation, you can use the `deleteConversation() This method takes two parameters. The unique id (UID/GUID) of the conversation to be deleted & the type (user/group) of conversation to be deleted. + +Deleting a conversation needs a connection — with offline support enabled, `deleteConversation()` is **not** queued for later. On success the conversation and its cached messages are removed from the on-device cache. See [Send While Offline](/sdk/android/v5/offline-send). + + ```java diff --git a/sdk/android/v5/delete-group.mdx b/sdk/android/v5/delete-group.mdx index b8d1af863..b1bdea83a 100644 --- a/sdk/android/v5/delete-group.mdx +++ b/sdk/android/v5/delete-group.mdx @@ -9,6 +9,10 @@ description: "Only a group admin can remove it, and this page shows the single p To delete a group you need to use the `deleteGroup()` method. The user must be an **Admin** of the group they are trying to delete. + +With offline support enabled, deleting a group removes that group's conversation from the on-device cache; any messages still queued to it are dropped and reported as failed with reason `DISCARDED`. See [Send While Offline](/sdk/android/v5/offline-send). + + ```java diff --git a/sdk/android/v5/delete-message.mdx b/sdk/android/v5/delete-message.mdx index 6db94d72b..d5caf2c25 100644 --- a/sdk/android/v5/delete-message.mdx +++ b/sdk/android/v5/delete-message.mdx @@ -16,6 +16,10 @@ While [deleting a message](/sdk/android/v5/delete-message#delete-a-message) is s In case you have to delete a message, you can use the `deleteMessage()` method. This method takes the message ID of the message to be deleted. + +Deleting a message needs a connection — with offline support enabled, `deleteMessage()` is **not** queued for later. On success the deletion is written to the on-device cache. See [Send While Offline](/sdk/android/v5/offline-send). + + ```java diff --git a/sdk/android/v5/edit-message.mdx b/sdk/android/v5/edit-message.mdx index fe6a8d7a6..f2c0f8023 100644 --- a/sdk/android/v5/edit-message.mdx +++ b/sdk/android/v5/edit-message.mdx @@ -16,6 +16,10 @@ While [editing a message](/sdk/android/v5/edit-message#edit-a-message) is straig In order to edit a message, you can use the `editMessage()` method. This method takes an object of the `BaseMessage` class. At the moment, you are only allowed to edit `TextMessage` and `CustomMessage`. Thus, the `BaseMessage` object must either be a Text or a Custom Message. + +Editing a message needs a connection — with offline support enabled, `editMessage()` is **not** queued for later. On success the edited message is updated in the on-device cache. See [Send While Offline](/sdk/android/v5/offline-send). + + ### Add/Update Tags While editing a message, you can update the tags associated with the Message. You can use the `setTags()` method to do so. The tags added while editing a message will replace the tags set when the message was sent. diff --git a/sdk/android/v5/group-kick-member.mdx b/sdk/android/v5/group-kick-member.mdx index 8a02b75a5..56beb8e2d 100644 --- a/sdk/android/v5/group-kick-member.mdx +++ b/sdk/android/v5/group-kick-member.mdx @@ -3,6 +3,8 @@ title: "Ban/Kick Member From A Group" description: "Shows the full set of group member actions, kicking, banning, unbanning, and scope updates, and who is allowed to perform each one." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + There are certain actions that can be performed on the group members: @@ -205,6 +207,8 @@ val bannedGroupMembersRequest = BannedGroupMembersRequestBuilder(GUID) ### Set Search Keyword +With offline support on, this filter is answered by the server only; the cached page is empty. + This method allows you to set the search string based on which the banned group members are to be fetched. @@ -232,6 +236,8 @@ Finally, once all the parameters are set to the builder class, you need to call Once you have the object of the `BannedGroupMembersRequest` class, you need to call the `fetchNext()` method. Calling this method will return a list of `GroupMember` objects containing n number of banned members where n is the limit set in the builder class. + + ```java @@ -241,10 +247,16 @@ private int limit = 30; bannedGroupMembersRequest = new BannedGroupMembersRequest.BannedGroupMembersRequestBuilder(GUID).setLimit(limit).build(); -bannedGroupMembersRequest.fetchNext(new CometChat.CallbackListener>(){ +bannedGroupMembersRequest.fetchNext(new CometChat.BannedGroupMemberFetchListener(){ + @Override + public void onCachedBannedGroupMembers(List bannedGroupMembers) { + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached banned members: " + bannedGroupMembers.size()); + } @Override - public void onSuccess(List list) { - Log.d(TAG, "Banned Group Member list fetched successfully: " + list.size()); + public void onServerBannedGroupMembers(List bannedGroupMembers) { + // Server page: reconcile your displayed list, keyed by uid. + Log.d(TAG, "Server banned members: " + bannedGroupMembers.size()); } @Override public void onError(CometChatException e) { @@ -263,9 +275,14 @@ val limit:Int=30 bannedGroupMembersRequest=BannedGroupMembersRequest.BannedGroupMembersRequestBuilder(GUID).setLimit(limit).build() -bannedGroupMembersRequest?.fetchNext(object :CometChat.CallbackListener>(){ - override fun onSuccess(p0: List?) { - Log.d(TAG, "Banned Group Member list fetched successfully: " + p0?.size) +bannedGroupMembersRequest?.fetchNext(object :CometChat.BannedGroupMemberFetchListener(){ + override fun onCachedBannedGroupMembers(bannedGroupMembers: List) { + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached banned members: " + bannedGroupMembers.size) + } + override fun onServerBannedGroupMembers(bannedGroupMembers: List) { + // Server page: reconcile your displayed list, keyed by uid. + Log.d(TAG, "Server banned members: " + bannedGroupMembers.size) } override fun onError(p0: CometChatException?) { Log.d(TAG, "Banned Group Member list fetching failed with exception: " + p0?.message) @@ -277,6 +294,15 @@ bannedGroupMembersRequest?.fetchNext(object :CometChat.CallbackListener +## Offline Behaviour + +With offline support enabled, `BannedGroupMembersRequest.fetchNext()` on a `BannedGroupMemberFetchListener` first delivers `onCachedBannedGroupMembers` from the device (empty when nothing was recorded), then `onServerBannedGroupMembers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `setSearchKeyword` | Server only — cached page is empty | + ## Real-Time Group Member Kicked/Banned Events *In other words, as a member of a group, how do I know when someone is banned/kicked when my app is running?* diff --git a/sdk/android/v5/leave-group.mdx b/sdk/android/v5/leave-group.mdx index 3f32e5a55..b14ae7beb 100644 --- a/sdk/android/v5/leave-group.mdx +++ b/sdk/android/v5/leave-group.mdx @@ -9,6 +9,10 @@ description: "Removing a group member or leaving a group yourself triggers event In order to stop receiving updates and messages for any particular joined group, you will have to leave the group using the `leaveGroup()` method. + +With offline support enabled, leaving a group clears that group's conversation from the on-device cache; any messages still queued to it are dropped and reported as failed with reason `DISCARDED`. See [Send While Offline](/sdk/android/v5/offline-send). + + ```java diff --git a/sdk/android/v5/mentions.mdx b/sdk/android/v5/mentions.mdx index 0eb709583..d64dfe892 100644 --- a/sdk/android/v5/mentions.mdx +++ b/sdk/android/v5/mentions.mdx @@ -5,6 +5,9 @@ description: "Mentions are a powerful tool for enhancing communication in messag +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; +import OfflineDeprecatedSend from '/snippets/sdk/offline-deprecated-send.mdx'; + ## Mentions Mentions are a powerful tool for enhancing communication in messaging platforms. They streamline interaction by allowing users to easily engage and collaborate with particular individuals, especially in group conversations. @@ -15,6 +18,8 @@ Mentions in messages enable users to refer to specific individuals within a conv Every User object has a String unique identifier associated with them which can be found in a property called uid. To mention a user in a message, the message text should contain the uid in following format:` <@uid:UID_OF_THE_USER>`. For example, to mention the user with UID cometchat-uid-1 in a text message, your text should be `"<@uid:cometchat-uid-1>"` + + ```java @@ -24,10 +29,15 @@ private String receiverType = CometChatConstants.RECEIVER_TYPE_USER; TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType); -CometChat.sendMessage(textMessage, new CometChat.CallbackListener() { +CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { + @Override + public void onMessagePending(BaseMessage message) { + Log.d(TAG, "Message queued"); + } + @Override - public void onSuccess(TextMessage textMessage) { - Log.d(TAG, "Message sent successfully: " + textMessage.toString()); + public void onMessageSent(BaseMessage message) { + Log.d(TAG, "Message sent successfully: " + message.toString()); } @Override @@ -46,9 +56,13 @@ private val messageText = "Hello <@uid:cometchat-uid-1>" private val receiverType = CometChatConstants.RECEIVER_TYPE_USER val textMessage = TextMessage(receiverID, messageText, receiverType) -CometChat.sendMessage(textMessage, object : CallbackListener() { - override fun onSuccess(textMessage: TextMessage) { - Log.d(TAG, "Message sent successfully: $textMessage") +CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + Log.d(TAG, "Message queued") + } + + override fun onMessageSent(message: BaseMessage?) { + Log.d(TAG, "Message sent successfully: $message") } override fun onError(e: CometChatException) { @@ -67,10 +81,15 @@ private String receiverType = CometChatConstants.RECEIVER_TYPE_GROUP; TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType); -CometChat.sendMessage(textMessage, new CometChat.CallbackListener() { +CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { + @Override + public void onMessagePending(BaseMessage message) { + Log.d(TAG, "Message queued"); + } + @Override - public void onSuccess(TextMessage textMessage) { - Log.d(TAG, "Message sent successfully: " + textMessage.toString()); + public void onMessageSent(BaseMessage message) { + Log.d(TAG, "Message sent successfully: " + message.toString()); } @Override @@ -89,9 +108,13 @@ private val messageText = "Hello <@uid:cometchat-uid-1>" private val receiverType = CometChatConstants.RECEIVER_TYPE_GROUP val textMessage = TextMessage(receiverID, messageText, receiverType) -CometChat.sendMessage(textMessage, object : CallbackListener() { - override fun onSuccess(textMessage: TextMessage) { - Log.d(TAG, "Message sent successfully: $textMessage") +CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + Log.d(TAG, "Message queued") + } + + override fun onMessageSent(message: BaseMessage?) { + Log.d(TAG, "Message sent successfully: $message") } override fun onError(e: CometChatException) { @@ -123,6 +146,8 @@ By default, the SDK will fetch all the messages irrespective of the fact that th To get a list of messages in a conversation where users are mentioned along with the user tags of the mentioned users. + + ```java @@ -134,9 +159,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .mentionsWithTagInfo(true) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { @Override - public void onSuccess(List messages) { + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + + @Override + public void onServerMessages(List messages) { for (BaseMessage messageObj: messages){ for (User user: messageObj.getMentionedUsers()){ Log.e(TAG, "tag: " + user.getTags()); @@ -163,8 +193,12 @@ val messagesRequest = MessagesRequest.MessagesRequestBuilder() .mentionsWithTagInfo(true) .build() -messagesRequest.fetchPrevious(object : CometChat.CallbackListener>() { - override fun onSuccess(messages: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(messages: List) { for (messageObj in messages) { for (user in messageObj.mentionedUsers) { Log.e(TAG, "tag: ${user.tags}") @@ -190,9 +224,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .mentionsWithTagInfo(true) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { @Override - public void onSuccess(List messages) { + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + + @Override + public void onServerMessages(List messages) { for (BaseMessage messageObj: messages){ for (User user: messageObj.getMentionedUsers()){ Log.e(TAG, "tag: " + user.getTags()); @@ -219,8 +258,12 @@ val messagesRequest = MessagesRequest.MessagesRequestBuilder() .mentionsWithTagInfo(true) .build() -messagesRequest.fetchPrevious(object : CometChat.CallbackListener>() { - override fun onSuccess(messages: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(messages: List) { for (messageObj in messages) { for (user in messageObj.mentionedUsers) { Log.e(TAG, "tag: ${user.tags}") @@ -253,9 +296,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .mentionsWithBlockedInfo(true) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { @Override - public void onSuccess(List messages) { + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + + @Override + public void onServerMessages(List messages) { for (BaseMessage messageObj: messages){ for (User user: messageObj.getMentionedUsers()){ Log.e(TAG, "isBlockedByMe: " + user.isBlockedByMe()); @@ -283,8 +331,12 @@ val messagesRequest = MessagesRequest.MessagesRequestBuilder() .mentionsWithBlockedInfo(true) .build() -messagesRequest.fetchPrevious(object : CometChat.CallbackListener>() { - override fun onSuccess(messages: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(messages: List) { for (messageObj in messages) { for (user in messageObj.mentionedUsers) { Log.e(TAG, "isBlockedByMe: ${user.isBlockedByMe}") @@ -311,9 +363,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .mentionsWithBlockedInfo(true) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { @Override - public void onSuccess(List messages) { + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + + @Override + public void onServerMessages(List messages) { for (BaseMessage messageObj: messages){ for (User user: messageObj.getMentionedUsers()){ Log.e(TAG, "isBlockedByMe: " + user.isBlockedByMe()); @@ -341,8 +398,12 @@ val messagesRequest = MessagesRequest.MessagesRequestBuilder() .mentionsWithBlockedInfo(true) .build() -messagesRequest.fetchPrevious(object : CometChat.CallbackListener>() { - override fun onSuccess(messages: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(messages: List) { for (messageObj in messages) { for (user in messageObj.mentionedUsers) { Log.e(TAG, "isBlockedByMe: ${user.isBlockedByMe}") @@ -402,3 +463,14 @@ message.hasMentionedMe + +## Offline Behaviour + +With offline support enabled, these mention fetches run in two phases: `onCachedMessages` delivers the cached page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +`mentionsWithTagInfo` and `mentionsWithBlockedInfo` enrich a read without narrowing it, so they never empty the cached page: the cached phase serves the matching messages as stored, and the server phase supplies the mentioned users' tag and blocked-relationship detail. + +| Builder method | Offline behaviour | +| --- | --- | +| `mentionsWithTagInfo(boolean)` | Cached | +| `mentionsWithBlockedInfo(boolean)` | Cached | diff --git a/sdk/android/v5/pin-conversation.mdx b/sdk/android/v5/pin-conversation.mdx index b2a86be56..4681f9cd8 100644 --- a/sdk/android/v5/pin-conversation.mdx +++ b/sdk/android/v5/pin-conversation.mdx @@ -4,6 +4,8 @@ sidebarTitle: "Pin A Conversation" description: "Pin and unpin conversations, fetch the pinned conversation list, and keep it in sync with the CometChat Android SDK." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + {/* TL;DR for Agents and Quick Reference */} @@ -17,7 +19,7 @@ val request = ConversationsRequest.ConversationsRequestBuilder() .setPinnedBy("system,me") .setLimit(30) .build() -request.fetchNext(callbackListener) // List +request.fetchNext(conversationFetchListener) // onCachedConversations + onServerConversations // Read pin state off a conversation val isPinned = conversation.isPinned // pinnedAt > 0 @@ -183,6 +185,8 @@ The default conversations list already orders pinned conversations at the top. T | `"system"` | Conversations pinned globally by the app (system pins). | | `"system,me"` | Both. | + + ```java @@ -191,10 +195,15 @@ ConversationsRequest conversationsRequest = new ConversationsRequest.Conversatio .setLimit(30) .build(); -conversationsRequest.fetchNext(new CometChat.CallbackListener>() { +conversationsRequest.fetchNext(new CometChat.ConversationFetchListener() { + @Override + public void onCachedConversations(List conversations) { + // Render the cached pinned list immediately. + } + @Override - public void onSuccess(List conversations) { - Log.d(TAG, "Pinned conversations: " + conversations.size()); + public void onServerConversations(List conversations) { + // Reconcile, keyed by conversation id. } @Override @@ -213,13 +222,17 @@ val conversationsRequest = ConversationsRequest.ConversationsRequestBuilder() .setLimit(30) .build() -conversationsRequest.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(conversations: List?) { - Log.d(TAG, "Pinned conversations: ${conversations?.size}") +conversationsRequest.fetchNext(object : CometChat.ConversationFetchListener() { + override fun onCachedConversations(conversations: List) { + // Render the cached pinned list immediately. } - override fun onError(e: CometChatException?) { - Log.e(TAG, "Fetch failed: ${e?.message}") + override fun onServerConversations(conversations: List) { + // Reconcile, keyed by conversation id. + } + + override fun onError(e: CometChatException) { + Log.e(TAG, "Fetch failed: ${e.message}") } }) ``` @@ -243,7 +256,7 @@ Every fetched `Conversation` carries its pin state. Register a `ConversationListener` to be notified when a conversation is pinned or unpinned, so your list can reorder without a refetch. -Today these callbacks fire on the **acting user's device** when a pin or unpin succeeds. Delivery to the user's other devices activates once server-side real-time delivery for conversation-pin events is rolled out — until then, other sessions pick up the change on their next conversations fetch. +Your own pin or unpin is reported through the `pinConversation()` / `unpinConversation()` success callback only — it does **not** fire `onConversationPinned` / `onConversationUnpinned` on the device that performed it. These callbacks fire on the user's **other** devices when a pin frame arrives there. @@ -383,6 +396,14 @@ if (CometChat.isPinConversationEnabled()) { --- +## Offline Behaviour + +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setPinnedBy()` request delivers a cached page first (`onCachedConversations`, even when empty), then the server list (`onServerConversations`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedConversations` holds the pinned conversations the cache already knows about, in the same order. + +| Builder method | Offline | +|---|---| +| `setPinnedBy` | Cached | + ## Next Steps diff --git a/sdk/android/v5/pin-message.mdx b/sdk/android/v5/pin-message.mdx index 330fc675d..d77605f2e 100644 --- a/sdk/android/v5/pin-message.mdx +++ b/sdk/android/v5/pin-message.mdx @@ -4,6 +4,8 @@ sidebarTitle: "Pin A Message" description: "Pin and unpin messages in a conversation, fetch the pinned list, and listen for pin events with the CometChat Android SDK." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + {/* TL;DR for Agents and Quick Reference */} @@ -18,7 +20,7 @@ val request = MessagesRequest.MessagesRequestBuilder() .setPinned(true) .setLimit(50) .build() -request.fetchNext(callbackListener) // List +request.fetchNext(messageFetchListener) // onCachedMessages + onServerMessages // Read pin state off a message val isPinned = message.isPinned // pinnedAt > 0 @@ -146,6 +148,8 @@ CometChat.unpinMessage(messageId, object : CometChat.CallbackListener + ```java @@ -157,10 +161,15 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setUID(UID) .build(); -messagesRequest.fetchNext(new CometChat.CallbackListener>() { +messagesRequest.fetchNext(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached pinned list immediately. + } + @Override - public void onSuccess(List messages) { - Log.d(TAG, "Pinned messages: " + messages.size()); + public void onServerMessages(List messages) { + // Reconcile, keyed by message id. } @Override @@ -182,10 +191,15 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setGUID(GUID) .build(); -messagesRequest.fetchNext(new CometChat.CallbackListener>() { +messagesRequest.fetchNext(new CometChat.MessageFetchListener() { @Override - public void onSuccess(List messages) { - Log.d(TAG, "Pinned messages: " + messages.size()); + public void onCachedMessages(List messages) { + // Render the cached pinned list immediately. + } + + @Override + public void onServerMessages(List messages) { + // Reconcile, keyed by message id. } @Override @@ -207,13 +221,17 @@ val messagesRequest = MessagesRequest.MessagesRequestBuilder() .setUID(UID) .build() -messagesRequest.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(messages: List?) { - Log.d(TAG, "Pinned messages: ${messages?.size}") +messagesRequest.fetchNext(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached pinned list immediately. } - override fun onError(e: CometChatException?) { - Log.e(TAG, "Pinned messages fetch failed: ${e?.message}") + override fun onServerMessages(messages: List) { + // Reconcile, keyed by message id. + } + + override fun onError(e: CometChatException) { + Log.e(TAG, "Pinned messages fetch failed: ${e.message}") } }) ``` @@ -230,13 +248,17 @@ val messagesRequest = MessagesRequest.MessagesRequestBuilder() .setGUID(GUID) .build() -messagesRequest.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(messages: List?) { - Log.d(TAG, "Pinned messages: ${messages?.size}") +messagesRequest.fetchNext(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached pinned list immediately. } - override fun onError(e: CometChatException?) { - Log.e(TAG, "Pinned messages fetch failed: ${e?.message}") + override fun onServerMessages(messages: List) { + // Reconcile, keyed by message id. + } + + override fun onError(e: CometChatException) { + Log.e(TAG, "Pinned messages fetch failed: ${e.message}") } }) ``` @@ -289,7 +311,7 @@ Editing a message preserves its pin. A message stores only its most recent pinne Register a `MessageListener` and override the pin callbacks. Each event delivers the full updated `BaseMessage`, so you can directly replace the message in your list. -Today these callbacks fire on the **acting user's device** when a pin or unpin succeeds. Delivery to other participants activates once server-side real-time delivery for pin events is rolled out — until then, other clients pick up pin changes on their next message fetch. +Your own pin or unpin is reported through the `pinMessage()` / `unpinMessage()` success callback only — it does **not** fire `onMessagePinned` / `onMessageUnpinned` on the device that performed it. These callbacks fire on **other** participants' devices when a pin frame arrives there. @@ -430,6 +452,14 @@ if (CometChat.isPinMessageEnabled()) { --- +## Offline Behaviour + +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setPinned(true)` request delivers a cached page first (`onCachedMessages`, even when empty) and then the server list (`onServerMessages`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedMessages` holds the conversation's cached pinned messages, newest pin first. The pinned list is answered from its own store, so other `MessagesRequestBuilder` filters are ignored, as on the server. + +| Builder method | Offline | +|---|---| +| `setPinned` (with `setUID` / `setGUID`) | Cached | + ## Next Steps diff --git a/sdk/android/v5/reactions.mdx b/sdk/android/v5/reactions.mdx index dcee8bfaf..110216631 100644 --- a/sdk/android/v5/reactions.mdx +++ b/sdk/android/v5/reactions.mdx @@ -3,6 +3,8 @@ title: "Reactions" description: "Enhance user engagement in your chat application with message reactions." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + Enhance user engagement in your chat application with message reactions. Users can express their emotions using reactions to messages. This feature allows users to add or remove reactions, and to fetch all reactions on a message. You can also listen to reaction events in real-time. Let's see how to work with reactions in CometChat's Android SDK. @@ -105,35 +107,41 @@ CometChat.removeReaction(messageId, emoji, object : CometChat.CallbackListener + ```java int limit = 30; int messageId = 1; -ReactionRequest reactionRequest = new ReactionRequest.ReactionRequestBuilder() - .setLimit(30) - .setMessageId(1) +ReactionsRequest reactionsRequest = new ReactionsRequest.ReactionsRequestBuilder() + .setLimit(limit) + .setMessageId(messageId) .build(); -reactionRequest.fetchNext(new CometChat.CallbackListener>() { +reactionsRequest.fetchNext(new CometChat.ReactionFetchListener() { + @Override + public void onCachedReactions(List reactions) { + // Cached page: fires first (even when empty). Render immediately. + Log.e(TAG, "Cached reactions: " + reactions.size()); + } + @Override - public void onSuccess(List reactions) { - Log.e(TAG, "Total Reactions: " + reactions.size()); - for (MessageReaction reaction : reactions) { - Log.e(TAG, "Reaction: " + reaction.getReaction()); - } + public void onServerReactions(List reactions) { + // Server page: reconcile your displayed list, keyed by reaction id. + Log.e(TAG, "Server reactions: " + reactions.size()); } @Override @@ -150,17 +158,20 @@ reactionRequest.fetchNext(new CometChat.CallbackListener>( val limit = 30 val messageId = 1 -val reactionRequest = ReactionRequest.ReactionRequestBuilder() +val reactionsRequest = ReactionsRequest.ReactionsRequestBuilder() .setLimit(limit) .setMessageId(messageId) .build() -reactionRequest.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(reactions: List?) { - Log.e(TAG, "Total Reactions: ${reactions?.size}") - reactions?.forEach { reaction -> - Log.e(TAG, "Reaction: ${reaction.reaction}") - } +reactionsRequest.fetchNext(object : CometChat.ReactionFetchListener() { + override fun onCachedReactions(reactions: List) { + // Cached page: fires first (even when empty). Render immediately. + Log.e(TAG, "Cached reactions: ${reactions.size}") + } + + override fun onServerReactions(reactions: List) { + // Server page: reconcile your displayed list, keyed by reaction id. + Log.e(TAG, "Server reactions: ${reactions.size}") } override fun onError(e: CometChatException?) { @@ -183,18 +194,22 @@ The `fetchPrevious()` method fetches the previous set of reactions for the messa int limit = 30; int messageId = 1; -ReactionRequest reactionRequest = new ReactionRequest.ReactionRequestBuilder() - .setLimit(30) - .setMessageId(1) +ReactionsRequest reactionsRequest = new ReactionsRequest.ReactionsRequestBuilder() + .setLimit(limit) + .setMessageId(messageId) .build(); -reactionRequest.fetchPrevious(new CometChat.CallbackListener>() { +reactionsRequest.fetchPrevious(new CometChat.ReactionFetchListener() { @Override - public void onSuccess(List reactions) { - Log.e(TAG, "Total Reactions: " + reactions.size()); - for (MessageReaction reaction : reactions) { - Log.e(TAG, "Reaction: " + reaction.getReaction()); - } + public void onCachedReactions(List reactions) { + // Cached page: fires first (even when empty). Render immediately. + Log.e(TAG, "Cached reactions: " + reactions.size()); + } + + @Override + public void onServerReactions(List reactions) { + // Server page: reconcile your displayed list, keyed by reaction id. + Log.e(TAG, "Server reactions: " + reactions.size()); } @Override @@ -211,17 +226,20 @@ reactionRequest.fetchPrevious(new CometChat.CallbackListener>() { - override fun onSuccess(reactions: List?) { - Log.e(TAG, "Total Reactions: ${reactions?.size}") - reactions?.forEach { reaction -> - Log.e(TAG, "Reaction: ${reaction.reaction}") - } +reactionsRequest.fetchPrevious(object : CometChat.ReactionFetchListener() { + override fun onCachedReactions(reactions: List) { + // Cached page: fires first (even when empty). Render immediately. + Log.e(TAG, "Cached reactions: ${reactions.size}") + } + + override fun onServerReactions(reactions: List) { + // Server page: reconcile your displayed list, keyed by reaction id. + Log.e(TAG, "Server reactions: ${reactions.size}") } override fun onError(e: CometChatException?) { @@ -234,6 +252,16 @@ reactionRequest.fetchPrevious(object : CometChat.CallbackListener +## Offline Behaviour + +With offline support enabled, `ReactionsRequest.fetchNext()` and `fetchPrevious()` on a `ReactionFetchListener` first deliver `onCachedReactions` from the device (empty when nothing was recorded), then `onServerReactions` with the live page to reconcile by reaction id. Each direction is cached independently, and offline a read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `setMessageId` | Cached | +| `setReaction` | Cached | + ## Real-time Reaction Events Keep the chat interactive with real-time updates for reactions. Register a listener for these events and make your UI responsive. diff --git a/sdk/android/v5/real-time-listeners.mdx b/sdk/android/v5/real-time-listeners.mdx index d3c2e597f..80efec755 100644 --- a/sdk/android/v5/real-time-listeners.mdx +++ b/sdk/android/v5/real-time-listeners.mdx @@ -165,6 +165,10 @@ CometChat.removeGroupListener(UNIQUE_LISTENER_ID) The `MessageListener` class provides you with live events related to messages. Below are the callback methods provided by the `MessageListener` class. + +With offline support enabled, `MessageListener` also receives `onMessagesSynced`, delivered after the SDK catches up on messages it missed while offline. See [Stay in Sync](/sdk/android/v5/offline-sync). + + | Method | Information | | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `onTextMessageReceived(TextMessage message)` | This event is triggered when a Text Message is received. | diff --git a/sdk/android/v5/receive-messages.mdx b/sdk/android/v5/receive-messages.mdx index 9f4034f46..1f25470a4 100644 --- a/sdk/android/v5/receive-messages.mdx +++ b/sdk/android/v5/receive-messages.mdx @@ -5,6 +5,8 @@ description: "Covers listening for live messages while the app is active and fet +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + Receiving messages with CometChat has two parts: 1. Adding a listener to receive [Real-time Messages](/sdk/android/v5/receive-messages#real-time-messages) when your app is running @@ -108,6 +110,12 @@ As a sender, you will not receive your own message in a real-time message event. + + +With offline support enabled, a registered `MessageListener` also receives `onMessagesSynced(List messages)` after the SDK catches up on reconnect, carrying the messages that arrived while the device was offline. See [Stay in Sync](/sdk/android/v5/offline-sync). + + + ### Receiving Card Messages `onCardMessageReceived(CardMessage cardMessage)` is triggered when a card message (category `"card"`) is received. Card Messages are **receive-only** — they are created via the Platform REST API or the Dashboard Bubble Builder, not sent from the SDK (see [Send a Message → Card Message](/sdk/android/v5/send-message#card-message)). @@ -213,6 +221,8 @@ Calling the `fetchNext()` method on the same object repeatedly allows you to fet ### Fetch Missed Messages of a particular one-on-one conversation + + ```java @@ -226,9 +236,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setUID(UID) .build(); -messagesRequest.fetchNext(new CometChat.CallbackListener>() { +messagesRequest.fetchNext(new CometChat.MessageFetchListener() { @Override - public void onSuccess(List list) { + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + + @Override + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " + @@ -262,8 +277,12 @@ val messagesRequest = MessagesRequestBuilder() .setUID(UID) .build() -messagesRequest.fetchNext(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchNext(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d( @@ -304,9 +323,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setGUID(GUID) .build(); -messagesRequest.fetchNext(new CometChat.CallbackListener>() { +messagesRequest.fetchNext(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List list) { + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " + ((TextMessage) message).toString()); @@ -338,8 +362,12 @@ messagesRequest = MessagesRequestBuilder() .setGUID(GUID) .build() -messagesRequest.fetchNext(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchNext(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d( @@ -384,9 +412,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setUID(UID) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List list) { + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " +((TextMessage) message).toString()); @@ -415,8 +448,12 @@ val messagesRequest = MessagesRequestBuilder() .setUID(UID) .build() -messagesRequest.fetchPrevious(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -449,9 +486,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setGUID(GUID) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List list) { + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " + ((TextMessage) message).toString()); @@ -480,8 +522,12 @@ val messagesRequest = MessagesRequestBuilder() .setGUID(GUID) .build() -messagesRequest.fetchPrevious(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -527,9 +573,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setUID(UID) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List list) { + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " + ((TextMessage) message).toString()); @@ -558,8 +609,12 @@ val messagesRequest = MessagesRequestBuilder() .setUID(UID) .build() -messagesRequest.fetchPrevious(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -596,9 +651,14 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setGUID(GUID) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { @Override - public void onSuccess(List list) { + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + + @Override + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " + ((TextMessage) message).toString()); @@ -627,8 +687,12 @@ val messagesRequest = MessagesRequestBuilder() .setGUID(GUID) .build() -messagesRequest.fetchPrevious(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -656,6 +720,8 @@ In other words, as a logged-in user, how do I search a message? In order to do this, you can use the `setSearchKeyword()` method. This method accepts string as input. When set, the SDK will fetch messages which match the `searchKeyword`. +With offline support on, this filter is answered by the server only; the cached page is empty. + By default, the searchKey is searched only in message text. However, if you enable `Conversation & Advanced Search`, the searchKey will be searched in message text, file name, mentions & mime type of the file. @@ -681,9 +747,14 @@ private String UID = "cometchat-uid-1"; MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder().setSearchKeyword("Hello").setUID(UID).setLimit(limit).build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List list) { + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " + ((TextMessage) message).toString()); @@ -709,8 +780,12 @@ val UID: String = "cometchat-uid-1" val messagesRequest = MessagesRequestBuilder().setSearchKeyword("Hello").setUID(UID).setLimit(limit).build() -messagesRequest.fetchPrevious(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -740,9 +815,14 @@ private String GUID = "cometchat-guid-1"; MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder().setSearchKeyword("Hello").setGUID(GUID).setLimit(limit).build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List list) { + public void onServerMessages(List list) { for (BaseMessage message : list) { if (message instanceof TextMessage) { Log.d(TAG, "Text message received successfully: " + ((TextMessage) message).toString()); @@ -768,8 +848,12 @@ val GUID = "cometchat-guid-1" val messagesRequest = MessagesRequestBuilder().setSearchKeyword("Hello").setGUID(GUID).setLimit(limit).build() -messagesRequest.fetchPrevious(object : CallbackListener>() { - override fun onSuccess(list: List) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached page immediately. + } + + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -1211,3 +1295,14 @@ override fun onError(e: CometChatException) { In the `onSuccess()` callback, you will receive a hashmap which will contain the `GUIDs` of the groups as the key and the unread message counts as the values. + +## Offline Behaviour + +With offline support enabled, message fetches run in two phases: `onCachedMessages` delivers a page from the on-device cache first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline behaviour | +| --- | --- | +| `setUnread(true)` | Cached | +| `setSearchKeyword(String)` | Server only — cached page is empty | + +The unread-count methods (`getUnreadMessageCountForUser`, `getUnreadMessageCountForGroup`, and the `getUnreadMessageCount` variants) are not two-phase reads and are unaffected. diff --git a/sdk/android/v5/retrieve-conversations.mdx b/sdk/android/v5/retrieve-conversations.mdx index 19c91ad07..e3b7194e8 100644 --- a/sdk/android/v5/retrieve-conversations.mdx +++ b/sdk/android/v5/retrieve-conversations.mdx @@ -3,7 +3,7 @@ title: "Retrieve Conversations" description: "Conversations provide the last messages for every one-on-one and group conversation the logged-in user is a part of." --- - +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; Conversations provide the last messages for every one-on-one and group conversation the logged-in user is a part of. This makes it easy for you to build a **Recent Chat** list. @@ -283,6 +283,8 @@ This feature is only available with `Conversation & Advanced Search`. The `Conve +With offline support on, this filter is answered by the server only; the cached page is empty. + ```java @@ -345,21 +347,27 @@ Once you have the object of the `ConversationsRequest` class, you need to call t A Maximum of only 50 Conversations can be fetched at once. + + ```java ConversationsRequest conversationsRequest = new ConversationsRequest.ConversationsRequestBuilder().setLimit(50).build(); -conversationsRequest.fetchNext(new CometChat.CallbackListener>() { +conversationsRequest.fetchNext(new CometChat.ConversationFetchListener() { @Override - public void onSuccess(List conversations) { - // Hanlde list of conversations + public void onCachedConversations(List conversations) { + // Render the cached list immediately. + } + @Override + public void onServerConversations(List conversations) { + // Reconcile, keyed by conversation id. } @Override public void onError(CometChatException e) { - // Hanlde failure + // Handle failure (offline: ERROR_INTERNET_UNAVAILABLE after the cached page). } }); ``` @@ -372,13 +380,17 @@ var conversationRequest: ConversationsRequest? = null val LIMIT:Int=30 conversationRequest = ConversationsRequest.ConversationsRequestBuilder().setLimit(LIMIT).build() -conversationRequest?.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(p0: List?) { - //Handle List of Conversations +conversationRequest?.fetchNext(object : CometChat.ConversationFetchListener() { + override fun onCachedConversations(conversations: List) { + // Render the cached list immediately. + } + + override fun onServerConversations(conversations: List) { + // Reconcile, keyed by conversation id. } - override fun onError(p0: CometChatException?) { - //Handle Failure + override fun onError(e: CometChatException) { + // Handle failure (offline: ERROR_INTERNET_UNAVAILABLE after the cached page). } }) ``` @@ -484,10 +496,15 @@ In order to fetch a specific conversation, you can use the `getConversation` met ```java -CometChat.getConversation(conversationWith, conversationType, new CometChat.CallbackListener() { +CometChat.getConversation(conversationWith, conversationType, new CometChat.ConversationDetailsListener() { @Override - public void onSuccess(Conversation conversation) { - // Handle getConversation success + public void onCachedConversation(Conversation conversation) { + // Fires only on a cache hit. + } + + @Override + public void onServerConversation(Conversation conversation) { + // Server copy, written through before delivery. } @Override @@ -501,17 +518,19 @@ CometChat.getConversation(conversationWith, conversationType, new CometChat.Call ```kotlin -CometChat.getConversation(conversationWith, conversationType, new CometChat.CallbackListener() { - @Override - public void onSuccess(Conversation conversation) { - +CometChat.getConversation(conversationWith, conversationType, object : CometChat.ConversationDetailsListener() { + override fun onCachedConversation(conversation: Conversation) { + // Fires only on a cache hit. } - @Override - public void onError(CometChatException e) { + override fun onServerConversation(conversation: Conversation) { + // Server copy, written through before delivery. + } + override fun onError(e: CometChatException) { + // Handle getConversation error } -}); +}) ``` @@ -544,3 +563,17 @@ val conversation = CometChatHelper.getConversationFromMessage(message) While converting a `Message` object to a `Conversation` object, the `unreadMessageCount` & `tags` will not be available in the `Conversation` object. The unread message count needs to be managed in your client-side code. + +## Offline Behaviour + +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` delivers a cached page first (`onCachedConversations`, even when empty) and then the server list (`onServerConversations`); offline, the read ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. The cached page is answered only when every builder filter on the request is locally computable — a single server-only filter blanks the cached page while the server phase still answers in full. + +| Builder method | Offline | +|---|---| +| `setConversationType` | Cached | +| `setTags` | Cached | +| `setUserTags` / `setGroupTags` | Cached | +| `setUnread` | Cached (last-known count; the server phase corrects it) | +| `includeBlockedUsers` / `withBlockedInfo` | Cached | +| `withUserAndGroupTags` / `withTags` | Cached (enrichment) | +| `setSearchKeyword` | Server only — cached page is empty | diff --git a/sdk/android/v5/retrieve-group-members.mdx b/sdk/android/v5/retrieve-group-members.mdx index 4432eceee..ef563a415 100644 --- a/sdk/android/v5/retrieve-group-members.mdx +++ b/sdk/android/v5/retrieve-group-members.mdx @@ -3,6 +3,8 @@ title: "Retrieve Group Members" description: "Shows how to fetch a group's member list with adjustable limits, search terms, and scope filters before loading more results." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + ## Retrieve the List of Group Members @@ -40,6 +42,8 @@ val groupMembersRequest = GroupMembersRequestBuilder(GUID) ### Set Search Keyword +With offline support on, this filter is answered by the server only; the cached page is empty. + This method allows you to set the search string based on which the group members are to be fetched. @@ -98,6 +102,8 @@ Finally, once all the parameters are set to the builder class, you need to call Once you have the object of the `GroupMembersRequest` class, you need to call the `fetchNext()` method. Calling this method will return a list of `GroupMember` objects containing N number of members depending on the limit set. + + ```java @@ -107,10 +113,16 @@ private int limit = 30; groupMembersRequest = new GroupMembersRequest.GroupMembersRequestBuilder(GUID).setLimit(limit).build(); -groupMembersRequest.fetchNext(new CometChat.CallbackListener>(){ +groupMembersRequest.fetchNext(new CometChat.GroupMemberFetchListener(){ + @Override + public void onCachedGroupMembers(List groupMembers) { + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached group members: " + groupMembers.size()); + } @Override - public void onSuccess(List list) { - Log.d(TAG, "Group Member list fetched successfully: " + list.size()); + public void onServerGroupMembers(List groupMembers) { + // Server page: reconcile your displayed list, keyed by uid. + Log.d(TAG, "Server group members: " + groupMembers.size()); } @Override public void onError(CometChatException e) { @@ -129,9 +141,14 @@ val limit:Int=30 groupMembersRequest=GroupMembersRequest.GroupMembersRequestBuilder(GUID).setLimit(limit).build() -groupMembersRequest?.fetchNext(object:CometChat.CallbackListener>(){ - override fun onSuccess(p0: List?){ - Log.d(TAG, "Group Member list fetched successfully: " + p0?.size) +groupMembersRequest?.fetchNext(object:CometChat.GroupMemberFetchListener(){ + override fun onCachedGroupMembers(groupMembers: List){ + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached group members: " + groupMembers.size) + } + override fun onServerGroupMembers(groupMembers: List){ + // Server page: reconcile your displayed list, keyed by uid. + Log.d(TAG, "Server group members: " + groupMembers.size) } override fun onError(p0: CometChatException?) { Log.d(TAG, "Group Member list fetching failed with exception: " + p0?.message) @@ -142,3 +159,14 @@ groupMembersRequest?.fetchNext(object:CometChat.CallbackListener + +## Offline Behaviour + +With offline support enabled, `GroupMembersRequest.fetchNext()` on a `GroupMemberFetchListener` first delivers `onCachedGroupMembers` from the device (empty when nothing was recorded), then `onServerGroupMembers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `setScopes` | Cached | +| `setSearchKeyword` | Server only — cached page is empty | +| `setStatus` | Server only — cached page is empty | diff --git a/sdk/android/v5/retrieve-groups.mdx b/sdk/android/v5/retrieve-groups.mdx index f3b664d2c..a45c01833 100644 --- a/sdk/android/v5/retrieve-groups.mdx +++ b/sdk/android/v5/retrieve-groups.mdx @@ -3,6 +3,8 @@ title: "Retrieve Groups" description: "Fetch the groups a user has joined or can join, filter them by keyword or tag, and get one group's details and online member count." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + ## Retrieve List of Groups @@ -38,6 +40,8 @@ val groupsRequest = GroupsRequestBuilder() ### Set Search Keyword +With offline support on, this filter is answered by the server only; the cached page is empty. + This method allows you to set the search string based on which the groups are to be fetched. @@ -152,6 +156,8 @@ Once you have the object of the `GroupsRequest` class, you need to call the `fet The list of groups fetched will only have the public and password type groups. The private groups will only be available if the user is a member of that private group. + + ```java @@ -160,10 +166,16 @@ private int limit = 30; groupsRequest = new GroupsRequest.GroupsRequestBuilder().setLimit(limit).build(); -groupsRequest.fetchNext(new CometChat.CallbackListener>() { +groupsRequest.fetchNext(new CometChat.GroupFetchListener() { +@Override +public void onCachedGroups(List groups) { + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached groups: " + groups.size()); +} @Override -public void onSuccess(List list) { - Log.d(TAG, "Groups list fetched successfully: " + list.size()); +public void onServerGroups(List groups) { + // Server page: reconcile your displayed list, keyed by guid. + Log.d(TAG, "Server groups: " + groups.size()); } @Override public void onError(CometChatException e) { @@ -179,9 +191,15 @@ public void onError(CometChatException e) { val limit = 30 val groupsRequest = GroupsRequestBuilder().setLimit(limit).build() -groupsRequest.fetchNext(object : CallbackListener>() { -override fun onSuccess(list: List) { - Log.d(TAG, "Groups list fetched successfully: " + list.size) +groupsRequest.fetchNext(object : CometChat.GroupFetchListener() { +override fun onCachedGroups(groups: List) { + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached groups: " + groups.size) +} + +override fun onServerGroups(groups: List) { + // Server page: reconcile your displayed list, keyed by guid. + Log.d(TAG, "Server groups: " + groups.size) } override fun onError(e: CometChatException) { @@ -293,3 +311,15 @@ override fun onError(e: CometChatException) { This method returns a `Hashmap` with the GUID of the group as the key and the online member count for that group as the value. + +## Offline Behaviour + +With offline support enabled, `GroupsRequest.fetchNext()` on a `GroupFetchListener` first delivers `onCachedGroups` from the device (empty when nothing was recorded), then `onServerGroups` with the live page to reconcile by `guid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `joinedOnly` | Cached | +| `setTags` | Cached | +| `withTags` | Cached | +| `setSearchKeyword` | Server only — cached page is empty | diff --git a/sdk/android/v5/retrieve-users.mdx b/sdk/android/v5/retrieve-users.mdx index f56779d59..83f5e8b91 100644 --- a/sdk/android/v5/retrieve-users.mdx +++ b/sdk/android/v5/retrieve-users.mdx @@ -3,6 +3,8 @@ title: "Retrieve Users" description: "Fetch the logged-in user's details and search or page through the full Android user directory with custom filters." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + ## Retrieve Logged In User Details @@ -61,6 +63,8 @@ val usersRequest = UsersRequestBuilder() ### Set Search Keyword +With offline support on, this filter is answered by the server only; the cached page is empty. + This method allows you to set the search string based on which the users are to be fetched. @@ -123,6 +127,8 @@ val usersRequest = UsersRequestBuilder() ### Set Status +With offline support on, this filter is answered by the server only; the cached page is empty. + The status based on which the users are to be fetched. The status parameter can contain one of the below two values: * CometChat.USER\_STATUS.ONLINE - will return the list of only online users. @@ -393,6 +399,8 @@ Finally, once all the parameters are set to the builder class, you need to call Once you have the object of the `UsersRequest` class, you need to call the `fetchNext()` method. Calling this method will return a list of `User` objects containing X number of users depending on the limit set. + + ```java @@ -401,10 +409,16 @@ private int limit = 30; usersRequest = new UsersRequest.UsersRequestBuilder().setLimit(limit).build(); -usersRequest.fetchNext(new CometChat.CallbackListener>() { +usersRequest.fetchNext(new CometChat.UserFetchListener() { @Override -public void onSuccess(List list) { - Log.d(TAG, "User list received: " + list.size()); +public void onCachedUsers(List users) { + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached users: " + users.size()); +} +@Override +public void onServerUsers(List users) { + // Server page: reconcile your displayed list, keyed by uid. + Log.d(TAG, "Server users: " + users.size()); } @Override public void onError(CometChatException e) { @@ -422,9 +436,15 @@ val limit = 30 usersRequest = UsersRequestBuilder().setLimit(limit).build() -usersRequest.fetchNext(object : CallbackListener>() { -override fun onSuccess(list: List) { - Log.d(TAG, "User list received: " + list.size) +usersRequest.fetchNext(object : CometChat.UserFetchListener() { +override fun onCachedUsers(users: List) { + // Cached page: fires first (even when empty). Render immediately. + Log.d(TAG, "Cached users: " + users.size) +} + +override fun onServerUsers(users: List) { + // Server page: reconcile your displayed list, keyed by uid. + Log.d(TAG, "Server users: " + users.size) } override fun onError(e: CometChatException) { @@ -525,3 +545,21 @@ override fun onError(e: CometChatException) { + +## Offline Behaviour + +With offline support enabled, `UsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the device — the pages the server last returned for this exact request, replayed on-device (empty when nothing was recorded) — then `onServerUsers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `setSearchKeyword` | Server only — cached page is empty | +| `setUserStatus` | Server only — cached page is empty | +| `hideBlockedUsers` | Cached | +| `setRoles` / `setRole` | Cached | +| `friendsOnly` | Cached | +| `setTags` | Cached | +| `withTags` | Cached | +| `setUIDs` | Cached | +| `sortBy` | Cached | +| `sortByOrder` | Cached | diff --git a/sdk/android/v5/save-message.mdx b/sdk/android/v5/save-message.mdx index 26e246501..2149f31a6 100644 --- a/sdk/android/v5/save-message.mdx +++ b/sdk/android/v5/save-message.mdx @@ -4,6 +4,8 @@ sidebarTitle: "Save A Message" description: "Save and unsave messages privately, fetch the cross-conversation saved list, and listen for save events with the CometChat Android SDK." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + {/* TL;DR for Agents and Quick Reference */} @@ -17,7 +19,7 @@ val request = MessagesRequest.MessagesRequestBuilder() .setSaved(true) .setLimit(50) .build() -request.fetchNext(callbackListener) // List +request.fetchNext(messageFetchListener) // onCachedMessages + onServerMessages // Read save state off a message val isSaved = message.isSaved // savedAt > 0 @@ -143,6 +145,8 @@ CometChat.unsaveMessage(messageId, object : CometChat.CallbackListener + ```java @@ -151,10 +155,15 @@ MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder() .setLimit(50) .build(); -messagesRequest.fetchNext(new CometChat.CallbackListener>() { +messagesRequest.fetchNext(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached saved list immediately. + } + @Override - public void onSuccess(List messages) { - Log.d(TAG, "Saved messages: " + messages.size()); + public void onServerMessages(List messages) { + // Reconcile, keyed by message id. } @Override @@ -173,13 +182,17 @@ val messagesRequest = MessagesRequest.MessagesRequestBuilder() .setLimit(50) .build() -messagesRequest.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(messages: List?) { - Log.d(TAG, "Saved messages: ${messages?.size}") +messagesRequest.fetchNext(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List) { + // Render the cached saved list immediately. } - override fun onError(e: CometChatException?) { - Log.e(TAG, "Saved messages fetch failed: ${e?.message}") + override fun onServerMessages(messages: List) { + // Reconcile, keyed by message id. + } + + override fun onError(e: CometChatException) { + Log.e(TAG, "Saved messages fetch failed: ${e.message}") } }) ``` @@ -230,7 +243,7 @@ if (message.isSaved) { Because saving is private, save events are never delivered to other participants. Register a `MessageListener` and override the save callbacks; each event delivers the full updated `BaseMessage`. -Today these callbacks fire on the **acting device** when a save or unsave succeeds. Delivery to the user's other devices activates once server-side real-time delivery for save events is rolled out — until then, other sessions pick up save changes on their next fetch. +Your own save or unsave is reported through the `saveMessage()` / `unsaveMessage()` success callback only — it does **not** fire `onMessageSaved` / `onMessageUnsaved` on the device that performed it. These callbacks fire on the user's **other** devices when a save frame arrives there. @@ -368,6 +381,14 @@ if (CometChat.isSaveMessageEnabled()) { --- +## Offline Behaviour + +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setSaved(true)` request delivers a cached page first (`onCachedMessages`, even when empty) and then the server list (`onServerMessages`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedMessages` holds the user's cached saved messages across all conversations. The saved list is answered from its own store, so other `MessagesRequestBuilder` filters are ignored, as on the server. + +| Builder method | Offline | +|---|---| +| `setSaved` | Cached | + ## Next Steps diff --git a/sdk/android/v5/send-message.mdx b/sdk/android/v5/send-message.mdx index aa6bc2bf3..f5e728696 100644 --- a/sdk/android/v5/send-message.mdx +++ b/sdk/android/v5/send-message.mdx @@ -3,6 +3,8 @@ title: "Send A Message" description: "Send text, media, and custom messages from Android, add metadata or tags, and receive interactive card messages." --- +import OfflineDeprecatedSend from '/snippets/sdk/offline-deprecated-send.mdx'; + Using CometChat, you can work with four types of messages. The first three you can send directly: @@ -100,6 +102,8 @@ textMessage.quotedMessage = // Pass base message object here that you want to q Once the text message object is ready, you need to use the `sendMessage()` method to send the text message to the recipient. + + ```java @@ -109,9 +113,14 @@ private String receiverType = CometChatConstants.RECEIVER_TYPE_USER; TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType); -CometChat.sendMessage(textMessage, new CometChat.CallbackListener() { +CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { + @Override + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + @Override - public void onSuccess(TextMessage textMessage) { + public void onMessageSent(BaseMessage textMessage) { Log.d(TAG, "Message sent successfully: " + textMessage.toString()); } @@ -132,8 +141,12 @@ private val receiverType = CometChatConstants.RECEIVER_TYPE_USER val textMessage = TextMessage(receiverID, messageText, receiverType) -CometChat.sendMessage(textMessage, object : CallbackListener() { - override fun onSuccess(textMessage: TextMessage) { +CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(textMessage: BaseMessage?) { Log.d(TAG, "Message sent successfully: $textMessage") } @@ -153,9 +166,14 @@ private String receiverType = CometChatConstants.RECEIVER_TYPE_GROUP; TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType); -CometChat.sendMessage(textMessage, new CometChat.CallbackListener() { +CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(TextMessage textMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage textMessage) { Log.d(TAG, "Message sent successfully: " + textMessage.toString()); } @@ -175,8 +193,12 @@ private val messageText = "Hello CometChat!" private val receiverType = CometChatConstants.RECEIVER_TYPE_GROUP val textMessage = TextMessage(receiverID, messageText, receiverType) -CometChat.sendMessage(textMessage, object : CallbackListener() { - override fun onSuccess(textMessage: TextMessage) { +CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(textMessage: BaseMessage?) { Log.d(TAG, "Message sent successfully: $textMessage") } @@ -293,9 +315,14 @@ private String filePath = "/storage/emulated/0/Download/CometChat.jpg"; MediaMessage mediaMessage = new MediaMessage(receiverID, new File(filePath), messageType, receiverType); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage mediaMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage mediaMessage) { Log.d(TAG, "Media message sent successfully: " + mediaMessage.toString()); } @@ -316,8 +343,12 @@ private val receiverType = CometChatConstants.RECEIVER_TYPE_USER private val filePath = "/storage/emulated/0/Download/CometChat.jpg" val mediaMessage = MediaMessage(receiverID, File(filePath), messageType, receiverType) -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage: MediaMessage) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage: BaseMessage?) { Log.d(TAG, "Media message sent successfully: " + mediaMessage.toString()) } @@ -338,9 +369,14 @@ private String filePath = "/storage/emulated/0/Download/CometChat.jpg"; MediaMessage mediaMessage = new MediaMessage(receiverID, new File(filePath), messageType, receiverType); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage mediaMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage mediaMessage) { Log.d(TAG, "Media message sent successfully: " + mediaMessage.toString()); } @@ -361,8 +397,12 @@ private val receiverType = CometChatConstants.RECEIVER_TYPE_GROUP private val filePath = "/storage/emulated/0/Download/CometChat.jpg" val mediaMessage = MediaMessage(receiverID, File(filePath), messageType, receiverType) -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage: MediaMessage) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage: BaseMessage?) { Log.d(TAG, "Media message sent successfully: " + mediaMessage.toString()) } @@ -401,9 +441,14 @@ attachment.setFileUrl("https://pngimg.com/uploads/mario/mario_PNG125.png"); mediaMessage.setAttachment(attachment); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage mediaMessage1) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage mediaMessage1) { Log.i(TAG, "onSuccess"); } @@ -429,8 +474,12 @@ attachment.fileUrl = "https://pngimg.com/uploads/mario/mario_PNG125.png" mediaMessage.attachment = attachment -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage1: MediaMessage?) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage1: BaseMessage?) { Log.i(TAG, "onSuccess") } @@ -455,9 +504,14 @@ attachment.setFileUrl("https://pngimg.com/uploads/mario/mario_PNG125.png"); mediaMessage.setAttachment(attachment); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage mediaMessage1) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage mediaMessage1) { Log.i(TAG, "onSuccess"); } @@ -483,8 +537,12 @@ attachment.fileUrl = "https://pngimg.com/uploads/mario/mario_PNG125.png" mediaMessage.attachment = attachment -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage1: MediaMessage?) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage1: BaseMessage?) { Log.i(TAG, "onSuccess") } @@ -526,9 +584,14 @@ files.add(new File("/storage/emulated/0/Download/3.jpg")); MediaMessage mediaMessage = new MediaMessage(receiverId, files, fileType, CometChatConstants.RECEIVER_TYPE_USER); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage mediaMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage mediaMessage) { Log.d(TAG, "Message Sent Successfully") } @@ -551,8 +614,12 @@ files.add(File("/storage/emulated/0/Download/3.jpg")) val mediaMessage = MediaMessage(receiverId, files, fileType, CometChatConstants.RECEIVER_TYPE_USER) -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage: MediaMessage?) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage: BaseMessage?) { Log.d(TAG, "Message Sent Successfully") } @@ -575,9 +642,14 @@ files.add(new File("/storage/emulated/0/Download/3.jpg")); MediaMessage mediaMessage = new MediaMessage(groupId, files, fileType, CometChatConstants.RECEIVER_TYPE_GROUP); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage mediaMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage mediaMessage) { Log.d(TAG, "Message Sent Successfully") } @@ -601,8 +673,12 @@ files.add(File("/storage/emulated/0/Download/3.jpg")) val mediaMessage = MediaMessage(groupId, files, fileType, CometChatConstants.RECEIVER_TYPE_GROUP) -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage: MediaMessage?) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage: BaseMessage?) { Log.d(TAG, "Message Sent Successfully") } @@ -646,9 +722,14 @@ for (int i = 0; i < 5; i++) { mediaMessage.setAttachments(attachments); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { + @Override + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + @Override - public void onSuccess(MediaMessage mediaMessage) { + public void onMessageSent(BaseMessage mediaMessage) { Log.d(TAG, "Message Sent Successfully") } @@ -678,8 +759,12 @@ for (i in 0..4) { mediaMessage.attachments = attachments -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage: MediaMessage?) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage: BaseMessage?) { Log.d(TAG, "Message Sent Successfully") } @@ -709,9 +794,14 @@ for (int i = 0; i < 5; i++) { mediaMessage.setAttachments(attachments); -CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { +CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage mediaMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage mediaMessage) { Log.d(TAG, "Message Sent Successfully") } @@ -741,8 +831,12 @@ for (i in 0..4) { mediaMessage.attachments = attachments -CometChat.sendMediaMessage(mediaMessage, object : CallbackListener() { - override fun onSuccess(mediaMessage: MediaMessage?) { +CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(mediaMessage: BaseMessage?) { Log.d(TAG, "Message Sent Successfully") } @@ -837,9 +931,14 @@ customData.put("longitude", "72.8777"); CustomMessage customMessage = new CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, customType, customData); -CometChat.sendCustomMessage(customMessage, new CometChat.CallbackListener() { +CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(CustomMessage customMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage customMessage) { Log.d(TAG, customMessage.toString()); } @@ -862,8 +961,12 @@ customData.put("latitude", "19.0760") customData.put("longitude", "72.8777") val customMessage = CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, customType, customData) -CometChat.sendCustomMessage(customMessage, object : CallbackListener() { - override fun onSuccess(customMessage: CustomMessage) { +CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(customMessage: BaseMessage?) { Log.d(TAG, customMessage.toString()) } @@ -886,9 +989,14 @@ customData.put("longitude", "72.8777"); CustomMessage customMessage = new CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, customType, customData); -CometChat.sendCustomMessage(customMessage, new CometChat.CallbackListener() { +CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(CustomMessage customMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage customMessage) { Log.d(TAG, customMessage.toString()); } @@ -911,8 +1019,12 @@ customData.put("latitude", "19.0760") customData.put("longitude", "72.8777") val customMessage = CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, customType, customData) -CometChat.sendCustomMessage(customMessage, object : CallbackListener() { - override fun onSuccess(customMessage: CustomMessage) { +CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(customMessage: BaseMessage?) { Log.d(TAG, customMessage.toString()) } @@ -949,9 +1061,14 @@ customData.put("longitude", "72.8777"); CustomMessage customMessage = new CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, customType, customData); customMessage.shouldUpdateConversation(false); -CometChat.sendCustomMessage(customMessage, new CometChat.CallbackListener() { +CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(CustomMessage customMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage customMessage) { Log.d(TAG, customMessage.toString()); } @@ -976,8 +1093,12 @@ customData.put("longitude", "72.8777") val customMessage = CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, customType, customData) customMessage.shouldUpdateConversation(false) -CometChat.sendCustomMessage(customMessage, object : CallbackListener() { - override fun onSuccess(customMessage: CustomMessage) { +CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(customMessage: BaseMessage?) { Log.d(TAG, customMessage.toString()) } @@ -1001,9 +1122,14 @@ customData.put("longitude", "72.8777"); CustomMessage customMessage = new CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, customType, customData); customMessage.shouldUpdateConversation(false); -CometChat.sendCustomMessage(customMessage, new CometChat.CallbackListener() { +CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(CustomMessage customMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage customMessage) { Log.d(TAG, customMessage.toString()); } @@ -1028,8 +1154,12 @@ customData.put("longitude", "72.8777") val customMessage = CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, customType, customData) customMessage.shouldUpdateConversation(false) -CometChat.sendCustomMessage(customMessage, object : CallbackListener() { - override fun onSuccess(customMessage: CustomMessage) { +CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(customMessage: BaseMessage?) { Log.d(TAG, customMessage.toString()) } @@ -1062,9 +1192,14 @@ customData.put("longitude", "72.8777"); CustomMessage customMessage = new CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, customType, customData); customMessage.setConversationText("Custom Notification Body"); -CometChat.sendCustomMessage(customMessage, new CometChat.CallbackListener() { +CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(CustomMessage customMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage customMessage) { Log.d(TAG, customMessage.toString()); } @@ -1089,8 +1224,12 @@ customData.put("longitude", "72.8777") val customMessage = CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, customType, customData) customMessage.setConversationText("Custom Notification Body") -CometChat.sendCustomMessage(customMessage, object : CallbackListener() { - override fun onSuccess(customMessage: CustomMessage) { +CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(customMessage: BaseMessage?) { Log.d(TAG, customMessage.toString()) } @@ -1114,9 +1253,14 @@ customData.put("longitude", "72.8777"); CustomMessage customMessage = new CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, customType, customData); customMessage.setConversationText("Custom Notification Body"); -CometChat.sendCustomMessage(customMessage, new CometChat.CallbackListener() { +CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(CustomMessage customMessage) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage customMessage) { Log.d(TAG, customMessage.toString()); } @@ -1141,8 +1285,12 @@ customData.put("longitude", "72.8777") val customMessage = CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, customType, customData) customMessage.setConversationText("Custom Notification Body") -CometChat.sendCustomMessage(customMessage, object : CallbackListener() { - override fun onSuccess(customMessage: CustomMessage) { +CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(customMessage: BaseMessage?) { Log.d(TAG, customMessage.toString()) } diff --git a/sdk/android/v5/setup.mdx b/sdk/android/v5/setup.mdx index abf688c82..1e206b31c 100644 --- a/sdk/android/v5/setup.mdx +++ b/sdk/android/v5/setup.mdx @@ -89,6 +89,10 @@ android { The `init()` method initialises the settings required for CometChat. The `init()` method takes the below parameters: + +Offline support (SDK 5.1.0+) is **off by default** and opt-in — initialization is unchanged until you turn it on. To cache chats on the device and keep them available without a connection, enable it when you build your settings. See [Enable Offline Support](/sdk/android/v5/offline-enable). + + 1. appID - You CometChat App ID 2. appSettings - An object of the AppSettings class can be created using the AppSettingsBuilder class. The region field is mandatory and can be set using the `setRegion()` method. diff --git a/sdk/android/v5/thread-subscription.mdx b/sdk/android/v5/thread-subscription.mdx index fef6affb1..a6033334a 100644 --- a/sdk/android/v5/thread-subscription.mdx +++ b/sdk/android/v5/thread-subscription.mdx @@ -4,6 +4,8 @@ sidebarTitle: "Thread Subscription" description: "Subscribe and unsubscribe from message threads, read subscription state off a message, and fetch participated threads with the CometChat Android SDK." --- +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; + {/* TL;DR for Agents and Quick Reference */} @@ -23,7 +25,7 @@ val request = ThreadsRequest.ThreadsRequestBuilder() .setParticipatedByMe(true) .setLimit(30) .build() -request.fetchNext(callbackListener) // List +request.fetchNext(threadsFetchListener) // onCachedThreads + onServerThreads ``` @@ -291,6 +293,8 @@ To build a thread inbox — one row per thread the user is part of — create a | `setGuid(String value)` | Scope the list to threads in this group. Mutually exclusive with `setUid()`. | | `setParticipatedByMe(boolean)` | Defaults to `true`. Only the threads the logged-in user participates in are returned. | + + ```java @@ -299,9 +303,14 @@ ThreadsRequest threadsRequest = new ThreadsRequest.ThreadsRequestBuilder() .setLimit(30) .build(); -threadsRequest.fetchNext(new CometChat.CallbackListener>() { +threadsRequest.fetchNext(new CometChat.ThreadsFetchListener() { + @Override + public void onCachedThreads(List threads) { + // Render the cached thread inbox immediately. + } + @Override - public void onSuccess(List threads) { + public void onServerThreads(List threads) { for (MessageThread thread : threads) { Log.d(TAG, "Thread " + thread.getParentMessageId() + " has " + thread.getReplyCount() + " replies"); @@ -324,15 +333,19 @@ val threadsRequest = ThreadsRequest.ThreadsRequestBuilder() .setLimit(30) .build() -threadsRequest.fetchNext(object : CometChat.CallbackListener>() { - override fun onSuccess(threads: List?) { - threads?.forEach { thread -> +threadsRequest.fetchNext(object : CometChat.ThreadsFetchListener() { + override fun onCachedThreads(threads: List) { + // Render the cached thread inbox immediately. + } + + override fun onServerThreads(threads: List) { + threads.forEach { thread -> Log.d(TAG, "Thread ${thread.parentMessageId} has ${thread.replyCount} replies") } } - override fun onError(e: CometChatException?) { - Log.e(TAG, "Threads fetch failed: ${e?.message}") + override fun onError(e: CometChatException) { + Log.e(TAG, "Threads fetch failed: ${e.message}") } }) ``` @@ -408,6 +421,15 @@ See [Notification Preferences](/notifications) for how to read and update a user --- +## Offline Behaviour + +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` delivers a cached page first (`onCachedThreads`, even when empty) and then the server list (`onServerThreads`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedThreads` holds the user's cached participated threads, in the same sort order; `setUid()` / `setGuid()` scope that list to one conversation. + +| Builder method | Offline | +|---|---| +| `setParticipatedByMe` | Cached | +| `setUid` / `setGuid` | Cached (scope) | + ## Next Steps diff --git a/sdk/android/v5/threaded-messages.mdx b/sdk/android/v5/threaded-messages.mdx index 14776903a..5f04dd494 100644 --- a/sdk/android/v5/threaded-messages.mdx +++ b/sdk/android/v5/threaded-messages.mdx @@ -5,6 +5,9 @@ description: "Messages that are started from a particular message are called Thr +import OfflineDeprecatedListener from '/snippets/sdk/offline-deprecated-listener.mdx'; +import OfflineDeprecatedSend from '/snippets/sdk/offline-deprecated-send.mdx'; + Messages that are started from a particular message are called Threaded messages or simply threads. Each Thread is attached to a message which is the Parent message for that thread. ## Send Message in a Thread @@ -23,15 +26,22 @@ This can be achieved using the `setParentMessageId()` method provided by the obj **Example to Send a Text Message in a thread in a user conversation.** + + ```java TextMessage textMessage = new TextMessage(UID, "Hello", CometChatConstants.RECEIVER_TYPE_USER); textMessage.setParentMessageId(100); -CometChat.sendMessage(textMessage, new CometChat.CallbackListener() { +CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { + @Override + public void onMessagePending(BaseMessage message) { + Log.d(TAG, "Text Message queued"); + } + @Override - public void onSuccess(TextMessage textMessage) { + public void onMessageSent(BaseMessage message) { Log.d(TAG, "Text Message Sent successfully"); } @@ -49,8 +59,12 @@ CometChat.sendMessage(textMessage, new CometChat.CallbackListener() val textMessage = TextMessage(UID, "Hello", CometChatConstants.RECEIVER_TYPE_USER) textMessage.parentMessageId = 100 -CometChat.sendMessage(textMessage, object : CallbackListener() { - override fun onSuccess(textMessage: TextMessage?) { +CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + Log.d(TAG, "Text Message queued") + } + + override fun onMessageSent(message: BaseMessage?) { Log.d(TAG, "Text Message Sent successfully") } @@ -145,6 +159,8 @@ The `MessageRequestBuilder` builds the `MessageRequest` object using the followi Once you have the `MessagesRequest` object, you can call the `fetchPrevious()` method to get the latest messages in the thread. In one iteration, a maximum of 100 messages can be fetched. If you wish to fetch the next set of messages, you need to call the fetchPrevious() method again on the same object. + + ```java @@ -153,9 +169,14 @@ MessagesRequest messagesRequest = neMessagesRequest.MessagesRequestBuilder() .setParentMessageId(100) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List messages) { + public void onServerMessages(List messages) { Log.d(TAG, "Messages for thread fetched successfully"); } @@ -175,8 +196,12 @@ val messagesRequest: MessagesRequest = neMessagesRequest.MessagesRequestBuilder( .setParentMessageId(100) .build() -messagesRequest.fetchPrevious(object : CallbackListener?>() { - override fun onSuccess(messages: List?) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List?) { + // Render the cached page immediately. + } + + override fun onServerMessages(messages: List?) { Log.d(TAG, "Messages for thread fetched successfully") } @@ -203,9 +228,14 @@ MessagesRequest messagesRequest = neMessagesRequest.MessagesRequestBuilder() .hideReplies(true) .build(); -messagesRequest.fetchPrevious(new CometChat.CallbackListener>() { +messagesRequest.fetchPrevious(new CometChat.MessageFetchListener() { + @Override + public void onCachedMessages(List messages) { + // Render the cached page immediately. + } + @Override - public void onSuccess(List messages) { + public void onServerMessages(List messages) { Log.d(TAG, "Messages for thread fetched successfully"); } @@ -226,8 +256,12 @@ val messagesRequest: MessagesRequest = neMessagesRequest.MessagesRequestBuilder( .hideReplies(true) .build() -messagesRequest.fetchPrevious(object : CallbackListener?>() { - override fun onSuccess(messages: List?) { +messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { + override fun onCachedMessages(messages: List?) { + // Render the cached page immediately. + } + + override fun onServerMessages(messages: List?) { Log.d(TAG, "Messages for thread fetched successfully") } @@ -243,6 +277,15 @@ messagesRequest.fetchPrevious(object : CallbackListener?>() { The above snippet will return messages between the logged in user and `cometchat-uid-1` excluding all the threaded messages belonging to the same conversation. +## Offline Behaviour + +With offline support enabled, thread fetches run in two phases: `onCachedMessages` delivers the cached thread page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +| Builder method | Offline behaviour | +| --- | --- | +| `setParentMessageId(int)` | Cached | +| `hideReplies(boolean)` | Cached | + ## Subscribe to a Thread Users can subscribe to or unsubscribe from a thread to control whether they are notified about its replies, and you can fetch the list of threads a user participates in to build a thread inbox. See [Thread Subscription](/sdk/android/v5/thread-subscription). diff --git a/sdk/android/v5/upload-files.mdx b/sdk/android/v5/upload-files.mdx index bd3b93287..c451e733e 100644 --- a/sdk/android/v5/upload-files.mdx +++ b/sdk/android/v5/upload-files.mdx @@ -4,6 +4,8 @@ sidebarTitle: "Upload Files" description: "Upload files directly to storage with per-file progress, remove, and retry through an UploadFileRequest — then send them as one or more media messages with multiple attachments." --- +import OfflineDeprecatedSend from '/snippets/sdk/offline-deprecated-send.mdx'; + `CometChat.createUploadFileRequest(receiverId, receiverType)` returns an **`UploadFileRequest`** — the entry point for uploading files **directly to storage** with **per-file progress, success, and failure**. Upload is **decoupled from sending**: each uploaded file yields an `Attachment` (carrying a hosted URL), which you then attach to a `MediaMessage` and send with [`sendMediaMessage()`](/sdk/android/v5/send-message#media-message). A request object is scoped to **one destination** (`receiverId` / `receiverType`) and **one upload batch**. This is the recommended way to build a **multi-attachment composer**: create a request, upload a batch of files, show a progress bar per file, let the user remove or retry individual files, then send them as a single media message with multiple attachments (or split across several). @@ -14,6 +16,10 @@ A request object is scoped to **one destination** (`receiverId` / `receiverType` The classic path (passing a `File`, or a list of files, straight to the `MediaMessage` constructor) uploads and sends in one blocking call — you get no progress, no per-file remove, and no per-file retry. An `UploadFileRequest` moves the upload out of the send call so you can drive a rich composer UI, then send instantly because the files are already hosted. + +With offline support enabled (SDK 5.1.0+), an upload batch and its files are persisted on the device: an in-progress batch survives an app restart and can be restored and retried, and a media message you send without a connection is durably queued and sent on reconnect. See [Send While Offline](/sdk/android/v5/offline-send). + + ## The upload-then-send flow @@ -380,6 +386,8 @@ There is no auto-clear on send or logout — call `clearAll()` yourself after a Once your files are uploaded, collect their `Attachment`s (from `request.getAttachments()` or from `result.getSuccessful()` in `onComplete`), set them on a `MediaMessage` built with the **no-file constructor**, and send. One batch can go out as a single multi-attachment message, or you can split the attachments across several messages — e.g. one message per media type using `getAttachmentsByType()`, reusing the same batch id across the sends. + + ```java @@ -418,9 +426,14 @@ UploadFileListener listener = new UploadFileListener() { mediaMessage.setCaption("Trip photos"); mediaMessage.setMuid(request.getBatchId()); // reconcile the message with its upload batch - CometChat.sendMediaMessage(mediaMessage, new CometChat.CallbackListener() { + CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override - public void onSuccess(MediaMessage message) { + public void onMessagePending(BaseMessage message) { + // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + } + + @Override + public void onMessageSent(BaseMessage message) { Log.d(TAG, "Media message sent successfully"); request.clearAll(); // release the batch } @@ -474,8 +487,12 @@ val listener = object : UploadFileListener() { mediaMessage.caption = "Trip photos" mediaMessage.muid = request.batchId // reconcile the message with its upload batch - CometChat.sendMediaMessage(mediaMessage, object : CometChat.CallbackListener() { - override fun onSuccess(message: MediaMessage) { + CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { + override fun onMessagePending(message: BaseMessage?) { + // Durably queued when offline; message?.status == MessageStatus.PENDING + } + + override fun onMessageSent(message: BaseMessage?) { Log.d(TAG, "Media message sent successfully") request.clearAll() // release the batch } From 8e7176bb7d6ff1cd19cf6e4b8455c457bb0668de Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 29 Oct 2026 19:57:27 +0530 Subject: [PATCH 03/42] docs(android-v5): remove outdated v2-to-v3 migration callout from Setup Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/setup.mdx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sdk/android/v5/setup.mdx b/sdk/android/v5/setup.mdx index 1e206b31c..0ddcef658 100644 --- a/sdk/android/v5/setup.mdx +++ b/sdk/android/v5/setup.mdx @@ -3,13 +3,6 @@ title: "Setup" description: "Set up the Android v5 SDK with app keys, Gradle dependencies, and initialization before building chat features." --- - - - -Skip the create new app step. Your existing v2 app can be migrated to v3. > > Follow steps mentioned in **Add the CometChat dependency** section below to upgrade to latest version of v3 - - - ### Get your Application Keys [Signup for CometChat](https://app.cometchat.com) and then: From a74cac35fd94bbdba3b2848e6285365bd55436ea Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 29 Oct 2026 20:05:16 +0530 Subject: [PATCH 04/42] docs(android-v5): point Setup, Overview and v4 migration at SDK 5.1.0 Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/overview.mdx | 4 ++-- sdk/android/v5/setup.mdx | 2 +- sdk/android/v5/upgrading-from-v4.mdx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/android/v5/overview.mdx b/sdk/android/v5/overview.mdx index d6e7e26c5..b9b3e0e57 100644 --- a/sdk/android/v5/overview.mdx +++ b/sdk/android/v5/overview.mdx @@ -4,7 +4,7 @@ description: "Build Android chat apps with CometChat SDK v5, including real-time --- -This is SDK v5 (`5.0.4`). It includes breaking changes from v4 — see the [Migration Guide](/sdk/android/v5/upgrading-from-v4) before upgrading. +This is SDK v5 (`5.1.0`). It includes breaking changes from v4 — see the [Migration Guide](/sdk/android/v5/upgrading-from-v4) before upgrading. This guide demonstrates how to add chat to an Android application. Before you begin, we strongly recommend you read the [Key Concepts](/sdk/android/v5/key-concepts) guide. @@ -67,7 +67,7 @@ Then, add CometChat to the **app level**`build.gradle` file in the `dependencies ```groovy dependencies { - implementation "com.cometchat:chat-sdk-android:5.0.4" + implementation "com.cometchat:chat-sdk-android:5.1.0" } ``` diff --git a/sdk/android/v5/setup.mdx b/sdk/android/v5/setup.mdx index 0ddcef658..1726e5a63 100644 --- a/sdk/android/v5/setup.mdx +++ b/sdk/android/v5/setup.mdx @@ -47,7 +47,7 @@ Then, add CometChat to the **app level** `build.gradle` file in the `dependencie ```java dependencies { - implementation "com.cometchat:chat-sdk-android:5.0.0" + implementation "com.cometchat:chat-sdk-android:5.1.0" } ``` diff --git a/sdk/android/v5/upgrading-from-v4.mdx b/sdk/android/v5/upgrading-from-v4.mdx index d29503022..a7e805f0d 100644 --- a/sdk/android/v5/upgrading-from-v4.mdx +++ b/sdk/android/v5/upgrading-from-v4.mdx @@ -12,7 +12,7 @@ Update your `build.gradle` dependency: ```groovy dependencies { - implementation "com.cometchat:chat-sdk-android:5.0.0" + implementation "com.cometchat:chat-sdk-android:5.1.0" } ``` From a14f0f7177c4595fb24d77d5a366509f5a2c45e3 Mon Sep 17 00:00:00 2001 From: Aditya Gokula Date: Fri, 30 Oct 2026 21:06:10 +0530 Subject: [PATCH 05/42] docs(android-v5): own pin, save and conversation pin also fire their listener on the acting device Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/pin-conversation.mdx | 2 +- sdk/android/v5/pin-message.mdx | 2 +- sdk/android/v5/save-message.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/pin-conversation.mdx b/sdk/android/v5/pin-conversation.mdx index 4681f9cd8..d2659a38f 100644 --- a/sdk/android/v5/pin-conversation.mdx +++ b/sdk/android/v5/pin-conversation.mdx @@ -256,7 +256,7 @@ Every fetched `Conversation` carries its pin state. Register a `ConversationListener` to be notified when a conversation is pinned or unpinned, so your list can reorder without a refetch. -Your own pin or unpin is reported through the `pinConversation()` / `unpinConversation()` success callback only — it does **not** fire `onConversationPinned` / `onConversationUnpinned` on the device that performed it. These callbacks fire on the user's **other** devices when a pin frame arrives there. +Your own pin or unpin fires `onConversationPinned` / `onConversationUnpinned` on the device that performed it, just before the `pinConversation()` / `unpinConversation()` success callback, so update your UI in one of the two, not both. These callbacks also fire on the user's **other** devices when a pin frame arrives there. diff --git a/sdk/android/v5/pin-message.mdx b/sdk/android/v5/pin-message.mdx index d77605f2e..7b7eb3ca3 100644 --- a/sdk/android/v5/pin-message.mdx +++ b/sdk/android/v5/pin-message.mdx @@ -311,7 +311,7 @@ Editing a message preserves its pin. A message stores only its most recent pinne Register a `MessageListener` and override the pin callbacks. Each event delivers the full updated `BaseMessage`, so you can directly replace the message in your list. -Your own pin or unpin is reported through the `pinMessage()` / `unpinMessage()` success callback only — it does **not** fire `onMessagePinned` / `onMessageUnpinned` on the device that performed it. These callbacks fire on **other** participants' devices when a pin frame arrives there. +Your own pin or unpin fires `onMessagePinned` / `onMessageUnpinned` on the device that performed it, just before the `pinMessage()` / `unpinMessage()` success callback, so update your UI in one of the two, not both. These callbacks also fire on **other** participants' devices when a pin frame arrives there. diff --git a/sdk/android/v5/save-message.mdx b/sdk/android/v5/save-message.mdx index 2149f31a6..52235453d 100644 --- a/sdk/android/v5/save-message.mdx +++ b/sdk/android/v5/save-message.mdx @@ -243,7 +243,7 @@ if (message.isSaved) { Because saving is private, save events are never delivered to other participants. Register a `MessageListener` and override the save callbacks; each event delivers the full updated `BaseMessage`. -Your own save or unsave is reported through the `saveMessage()` / `unsaveMessage()` success callback only — it does **not** fire `onMessageSaved` / `onMessageUnsaved` on the device that performed it. These callbacks fire on the user's **other** devices when a save frame arrives there. +Your own save or unsave fires `onMessageSaved` / `onMessageUnsaved` on the device that performed it, just before the `saveMessage()` / `unsaveMessage()` success callback, so update your UI in one of the two, not both. These callbacks also fire on the user's **other** devices when a save frame arrives there. From de5f9790e28651e28d44e1f7836471523d245dfd Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 31 Oct 2026 17:08:32 +0530 Subject: [PATCH 06/42] docs(android-v5): clarify the v4 breaking-changes callout on the overview Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/android/v5/overview.mdx b/sdk/android/v5/overview.mdx index b9b3e0e57..53aa39bc9 100644 --- a/sdk/android/v5/overview.mdx +++ b/sdk/android/v5/overview.mdx @@ -4,7 +4,7 @@ description: "Build Android chat apps with CometChat SDK v5, including real-time --- -This is SDK v5 (`5.1.0`). It includes breaking changes from v4 — see the [Migration Guide](/sdk/android/v5/upgrading-from-v4) before upgrading. +The v5 Android Chat SDK has breaking changes if you are migrating from v4. Please refer to [Upgrading from v4](/sdk/android/v5/upgrading-from-v4). This guide demonstrates how to add chat to an Android application. Before you begin, we strongly recommend you read the [Key Concepts](/sdk/android/v5/key-concepts) guide. From 4d1f2d8ed5bd1ed3969f3fca0fe20b2a2d4b4848 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 31 Oct 2026 23:00:54 +0530 Subject: [PATCH 07/42] docs(android-v5): say the new send and getConversation listeners need SDK 5.1.0 Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/retrieve-conversations.mdx | 4 ++++ snippets/sdk/offline-deprecated-send.mdx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/android/v5/retrieve-conversations.mdx b/sdk/android/v5/retrieve-conversations.mdx index e3b7194e8..4a8b52f6a 100644 --- a/sdk/android/v5/retrieve-conversations.mdx +++ b/sdk/android/v5/retrieve-conversations.mdx @@ -493,6 +493,10 @@ In order to fetch a specific conversation, you can use the `getConversation` met * user - Only fetches user conversation. * group - Only fetches group conversations. + +**Starting with v5.1.0, the `CallbackListener` overload of `getConversation()` is deprecated.** Use `ConversationDetailsListener` instead; it requires SDK 5.1.0 or later. The server copy arrives in `onServerConversation` instead of `onSuccess`, and `onError` is unchanged. With offline support on, `onCachedConversation` first delivers the cached copy when there is one. On 5.0.x, keep using `CallbackListener`. + + ```java diff --git a/snippets/sdk/offline-deprecated-send.mdx b/snippets/sdk/offline-deprecated-send.mdx index bfc8893da..b020c23b3 100644 --- a/snippets/sdk/offline-deprecated-send.mdx +++ b/snippets/sdk/offline-deprecated-send.mdx @@ -1,3 +1,3 @@ -**Starting with v5.1.0, the `CallbackListener` overloads of {method} are deprecated.** Use `MessageSentListener` instead. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the sent message arrives in `onMessageSent` instead of `onSuccess`. With offline support on, `onMessagePending` fires first, and a message sent without a connection is queued and sent when the connection returns. See [Send While Offline](/sdk/android/v5/offline-send). +**Starting with v5.1.0, the `CallbackListener` overloads of {method} are deprecated.** Use `MessageSentListener` instead; it requires SDK 5.1.0 or later. On 5.0.x, keep using `CallbackListener` and handle the sent message in `onSuccess` where these examples use `onMessageSent`. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the sent message arrives in `onMessageSent` instead of `onSuccess`. With offline support on, `onMessagePending` fires first, and a message sent without a connection is queued and sent when the connection returns. See [Send While Offline](/sdk/android/v5/offline-send). From 0ea921d2c41823be6bedb2042412c907ef1c16c8 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 31 Oct 2026 23:19:44 +0530 Subject: [PATCH 08/42] =?UTF-8?q?docs(android-v5):=20offline=20overview=20?= =?UTF-8?q?=E2=80=94=20single-item=20cache=20reads,=20outbox=20wipe,=20dep?= =?UTF-8?q?recated=20overloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-overview.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/offline-overview.mdx b/sdk/android/v5/offline-overview.mdx index c91476ea1..a21ab0ca8 100644 --- a/sdk/android/v5/offline-overview.mdx +++ b/sdk/android/v5/offline-overview.mdx @@ -38,7 +38,7 @@ Three ideas carry the whole feature. -Every read goes through the local cache first. The SDK delivers the cached page immediately — even when it is empty — then fetches from the server and delivers the fresh result. Your UI renders what is on the device without waiting for the network, then updates when the server responds. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +Every read goes through the local cache first. For list reads the SDK delivers the cached page immediately, even when it is empty. Single-item lookups (`getMessageDetails`, `getConversation`) deliver a cached result only when that item is already cached. The SDK then fetches from the server and delivers the fresh result. Your UI renders what is on the device without waiting for the network, then updates when the server responds. See [Show Cached Data](/sdk/android/v5/offline-cached-data). A message sent while offline is saved to a durable outbox and reported as pending instead of failing. It survives app restarts and is sent when the connection returns. See [Send While Offline](/sdk/android/v5/offline-send). @@ -62,14 +62,14 @@ flowchart LR ## What changes in your app -With offline support enabled, your fetch and send callbacks fire **twice** on the read path and gain a pending stage on the send path. To receive these cached and pending results you must move to the new listeners — `MessageFetchListener`, `ConversationFetchListener`, `MessageSentListener`, and the others listed on each page. The old `CallbackListener` overloads of the two-phase fetches and sends are deprecated in 5.1.0 but keep working. With offline support off, every API behaves exactly as it did before. +With offline support enabled, your fetch and send callbacks fire **twice** on the read path and gain a pending stage on the send path. To receive these cached and pending results you must move to the new listeners — `MessageFetchListener`, `ConversationFetchListener`, `MessageSentListener`, and the others listed on each page. The old `CallbackListener` overloads of the two-phase fetches and sends are deprecated in 5.1.0 but keep working: they receive only the server result, and a send through them is never queued. With offline support off, every API behaves exactly as it did before. ## One cache per user The SDK keeps a single local cache for the user who is logged in. - Logging the **same** user back in reuses that cache, so the data is there instantly on next launch. -- Logging out, a session expiring, or logging in as a **different** user wipes the cache. You never clear it yourself — the SDK does it. +- Logging out, a session expiring, or logging in as a **different** user wipes the cache, including any unsent messages still in the outbox, which are lost. You never clear it yourself — the SDK does it. See [Stay in Sync](/sdk/android/v5/offline-sync) for cache resets and logout behaviour. From 2c3e4207d34752399f6d119cad95882ea1072d39 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 16:17:04 +0530 Subject: [PATCH 09/42] =?UTF-8?q?docs(android-v5):=20offline=20enable=20?= =?UTF-8?q?=E2=80=94=20when=20isOfflineEnabled=20is=20false,=20Gradle=20ta?= =?UTF-8?q?b=20titles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-enable.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/offline-enable.mdx b/sdk/android/v5/offline-enable.mdx index 0e6216cba..ca96014a1 100644 --- a/sdk/android/v5/offline-enable.mdx +++ b/sdk/android/v5/offline-enable.mdx @@ -37,12 +37,12 @@ Offline support is **off by default**. You turn it on once, at initialization, b Offline support requires CometChat Android SDK **5.1.0** or later (minSdk 21). Add or update the dependency: - + ```groovy implementation "com.cometchat:chat-sdk-android:5.1.0" ``` - + ```kotlin implementation("com.cometchat:chat-sdk-android:5.1.0") ``` @@ -138,7 +138,7 @@ CometChat.init(context, "APP_ID", appSettings, object : CometChat.CallbackListen -Call `CometChat.isOfflineEnabled()`. It returns `true` only while offline support is actually active for the logged-in user — so it is `false` before anyone logs in, even when you enabled it in `AppSettings`. Read it at the moment of each decision and keep no copy of the result. +Call `CometChat.isOfflineEnabled()`. It returns `true` only while offline support is actually active for the logged-in user. It is `false` before anyone logs in (even when you enabled it in `AppSettings`), after logout or session expiry, when the encrypted cache cannot open, and when CometChat has turned offline support off for your app from the server. Turning it off from the server wipes the cache, including any unsent messages. Read it at the moment of each decision and keep no copy of the result. From 59959c75b429ec659a0ee19cd18825959629cb3e Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 16:20:05 +0530 Subject: [PATCH 10/42] =?UTF-8?q?docs(android-v5):=20offline=20enable=20?= =?UTF-8?q?=E2=80=94=20drop=20internal=20server-side=20behaviour=20from=20?= =?UTF-8?q?isOfflineEnabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-enable.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/android/v5/offline-enable.mdx b/sdk/android/v5/offline-enable.mdx index ca96014a1..a4b81764b 100644 --- a/sdk/android/v5/offline-enable.mdx +++ b/sdk/android/v5/offline-enable.mdx @@ -138,7 +138,7 @@ CometChat.init(context, "APP_ID", appSettings, object : CometChat.CallbackListen -Call `CometChat.isOfflineEnabled()`. It returns `true` only while offline support is actually active for the logged-in user. It is `false` before anyone logs in (even when you enabled it in `AppSettings`), after logout or session expiry, when the encrypted cache cannot open, and when CometChat has turned offline support off for your app from the server. Turning it off from the server wipes the cache, including any unsent messages. Read it at the moment of each decision and keep no copy of the result. +Call `CometChat.isOfflineEnabled()`. It returns `true` only while offline support is actually active for the logged-in user. It is `false` before anyone logs in (even when you enabled it in `AppSettings`), after logout or session expiry, and when the encrypted cache cannot open. Read it at the moment of each decision and keep no copy of the result. From 17d85260404c34541d727f534b7d4bd54c5d776d Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:15:02 +0530 Subject: [PATCH 11/42] =?UTF-8?q?docs(android-v5):=20offline=20enable=20?= =?UTF-8?q?=E2=80=94=20remove=20the=20settings-file=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-enable.mdx | 45 ------------------------------- 1 file changed, 45 deletions(-) diff --git a/sdk/android/v5/offline-enable.mdx b/sdk/android/v5/offline-enable.mdx index a4b81764b..4cd2e7bd7 100644 --- a/sdk/android/v5/offline-enable.mdx +++ b/sdk/android/v5/offline-enable.mdx @@ -158,51 +158,6 @@ Log.d(TAG, "Offline support active: $active") -## Enable from a settings file - -If you initialize with `CometChat.initFromSettings(...)`, which reads `cometchat-settings.json` from your app's `assets/`, add an `offline` key under `chatSDK`. It accepts a boolean shorthand or a full configuration object. - -```json cometchat-settings.json -{ - "appId": "APP_ID", - "region": "REGION", - "chatSDK": { - "offline": true - } -} -``` - - - -```java -CometChat.initFromSettings(context, new CometChat.CallbackListener() { - @Override - public void onSuccess(String successMessage) { - Log.d(TAG, "Initialization completed successfully"); - } - - @Override - public void onError(CometChatException e) { - Log.e(TAG, "Initialization failed: " + e.getMessage()); - } -}); -``` - - -```kotlin -CometChat.initFromSettings(context, object : CometChat.CallbackListener() { - override fun onSuccess(successMessage: String?) { - Log.d(TAG, "Initialization completed successfully") - } - - override fun onError(e: CometChatException?) { - Log.e(TAG, "Initialization failed: ${e?.message}") - } -}) -``` - - - ## Next steps Offline support is now active for the logged-in user. Continue with [Show Cached Data](/sdk/android/v5/offline-cached-data) to adopt the new fetch listeners, and [Send While Offline](/sdk/android/v5/offline-send) for the durable outbox. Tune caps, TTLs, media, and encryption from the [Configuration & API Reference](/sdk/android/v5/offline-reference). From 412bbc48733378dfb37ae51a0873d1823321f096 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:39:12 +0530 Subject: [PATCH 12/42] =?UTF-8?q?docs(android-v5):=20show=20cached=20data?= =?UTF-8?q?=20=E2=80=94=20overlapping=20fetches,=20server=20errors,=20bloc?= =?UTF-8?q?ked=20users,=20reactions,=20server-only=20filters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-cached-data.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/android/v5/offline-cached-data.mdx b/sdk/android/v5/offline-cached-data.mdx index 14a0d9dde..3ff81c98c 100644 --- a/sdk/android/v5/offline-cached-data.mdx +++ b/sdk/android/v5/offline-cached-data.mdx @@ -43,13 +43,13 @@ Each read fires **up to two** callbacks, always in this order: The SDK delivers a page from the local cache **first**, synchronously-fast. It fires **even when the page is empty** — an empty cached page is indistinguishable from a cache miss, by design. Render it right away. -After a successful server response, the SDK delivers the server's copy for the **same slice** and writes it through to the cache before the callback. Reconcile your displayed list against it. +After a successful server response, the SDK delivers the server's copy for the **same slice** and writes it through to the cache before the callback. Reconcile your displayed list against it. (Blocked users are the exception: their server page is delivered but not written to the cache, which keeps that list current through its own background refresh.) Two rules follow from this: -- **Offline, the read ends with an error.** After the cached page, a read whose server phase cannot be produced because the device is offline settles with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)`. The call always settles — it never hangs. +- **Offline, the read ends with an error.** After the cached page, a read that gets no answer because the device is offline settles with `onError`, code `ERROR_INTERNET_UNAVAILABLE`. A real server error reaches `onError` unchanged. The call always settles, as long as you wait for each fetch to return before starting the next one on the same request object. - **The server phase never fires offline.** It fires only on a genuine server response. Never treat a cached page as "end of history"; an empty cached page only means the end of the *cached* window. @@ -237,7 +237,7 @@ request.fetchNext(object : CometChat.UserFetchListener() { -For `ReactionsRequest`, `fetchPrevious` and `fetchNext` are cached independently — each direction keeps its own cached pages. Users, groups, members, banned members, reactions, and the notification feed are served from a per-request snapshot rather than a shared store; per-builder details live on each request's own page and in the [Offline Configuration & API Reference](/sdk/android/v5/offline-reference). +For `ReactionsRequest`, use one request object per direction: an object caches only the first direction it fetches, and its cached phase stays empty in the other direction. Users, groups, members, banned members, reactions, and the notification feed are served from a per-request snapshot rather than a shared store; per-builder details live on each request's own page and in the [Offline Configuration & API Reference](/sdk/android/v5/offline-reference). @@ -330,7 +330,7 @@ Key your list by id so the two phases merge cleanly: update items that changed, The cache answers a read **only if every filter on it is locally computable**. A single server-only predicate makes the entire cached phase return an empty list — with no error and no warning — while the server phase still answers in full. The guiding rule is *better empty than wrong*. -The most common server-only predicate is `searchKeyword`, which blanks the cached phase on every request. Other examples include `hasLinks`, `updatesOnly`, and `interactionGoalCompletedOnly` on `MessagesRequest`, and the presence filters (`UsersRequest.setUserStatus`, `GroupMembersRequest.setStatus`) on the entity lists. +The most common server-only predicate is `searchKeyword`, which blanks the cached phase on any request that carries a search keyword (threads, reactions and the notification feed never carry one). Other examples include `hasLinks`, `updatesOnly`, and `setInteractionGoalCompletedOnly` on `MessagesRequest`, setting a `UID` and a `GUID` together outside a thread (one sender's messages in a group), and the presence filters (`UsersRequest.setUserStatus`, `GroupMembersRequest.setStatus`) on the entity lists. From 5639fd12c36217b7e789d0d66b1fa8ec40f5b4fd Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:44:14 +0530 Subject: [PATCH 13/42] =?UTF-8?q?docs(android-v5):=20send=20while=20offlin?= =?UTF-8?q?e=20=E2=80=94=20lifecycle,=20auto-send=20conditions,=20failure?= =?UTF-8?q?=20reasons,=20queue=20limits,=20snippets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-send.mdx | 46 ++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/sdk/android/v5/offline-send.mdx b/sdk/android/v5/offline-send.mdx index 21d0b2902..8ed24aec1 100644 --- a/sdk/android/v5/offline-send.mdx +++ b/sdk/android/v5/offline-send.mdx @@ -38,7 +38,7 @@ CometChat.addOfflineMessageListener("LISTENER_ID", object : CometChat.OfflineMes -With offline support enabled, a message you send while the device has no connection is **durably queued on the device** instead of failing. It keeps its place across an app restart and is sent automatically when the connection returns. This page shows the offline-aware send listener, how to read a message's status, how to inspect and recover the send queue, and the queue limits. +With offline support enabled, a message you send while the device has no connection is **durably queued on the device** instead of failing. It keeps its place across an app restart and is sent automatically once the connection is back and stable (with `autoSendPendingOnReconnect` on, the default). This page shows the offline-aware send listener, how to read a message's status, how to inspect and recover the send queue, and the queue limits. @@ -50,7 +50,7 @@ Offline support is **off by default** and requires SDK **5.1.0** or newer. Queui ## Send a Message -Pass a `MessageSentListener` to `sendMessage`. Its lifecycle is `onMessagePending` first (the message is durably queued), then exactly one final outcome — `onMessageSent` (the server accepted it) or, for a real failure, `onError`. +Pass a `MessageSentListener` to `sendMessage`. Its lifecycle is normally `onMessagePending` first (the message is durably queued), then its outcome: `onMessageSent` (the server accepted it) or `onError`. Offline, `onError` is a queued-not-failed notice and `onMessageSent` still fires later on reconnect, so both can fire for one send (see below). If the message cannot be queued at all, for example because its media could not be copied (code `ERROR_OFFLINE_MEDIA_COPY_FAILED`, reason `PERMANENT`), only `onError` fires. @@ -133,13 +133,13 @@ CometChat.sendInteractiveMessage(interactiveMessage, listener) // InteractiveMes ## Queued, Not Failed -When the device is offline, the send still fires `onMessagePending`, then fires `onError` **once** with the code `ERROR_INTERNET_UNAVAILABLE`. This is a **notification, not a failure**: the message stays durably `PENDING`, and `onMessageSent` fires **later**, on reconnect, when it actually goes. +When the device is offline, the send still fires `onMessagePending`, then fires `onError` with the code `ERROR_INTERNET_UNAVAILABLE` for that attempt. This is a **notification, not a failure**: the message stays durably `PENDING`, and `onMessageSent` fires **later**, on reconnect, when it actually goes. This is the default behaviour, with `autoSendPendingOnReconnect` on. With it off, an offline send becomes `FAILED` (`TRANSIENT`) right away, and you recover it with `retryMessage`. So for a single send, `onError` and `onMessageSent` can **both** fire (offline, then reconnect), and `onError` does not always mean the message failed. Always read `message.getStatus()` to tell the two apart: | `status` after `onError` | Meaning | What to do | |---|---|---| -| `PENDING` | Queued; not sent yet. Will auto-send on reconnect. | Show as sending/queued; nothing to do. | +| `PENDING` | Queued; not sent yet. Auto-sends on reconnect (with `autoSendPendingOnReconnect` on, the default). | Show as sending/queued; nothing to do. | | `FAILED` | A real failure; will not retry on its own. | Offer the user a manual retry (see below). | @@ -147,9 +147,10 @@ So for a single send, `onError` and `onMessageSent` can **both** fire (offline, ```java @Override public void onError(CometChatException e) { - if (message.getStatus() == MessageStatus.PENDING) { + // textMessage is the message you passed to sendMessage; the SDK updates its status in place. + if (textMessage.getStatus() == MessageStatus.PENDING) { // Queued offline — it will send automatically on reconnect - } else if (message.getStatus() == MessageStatus.FAILED) { + } else if (textMessage.getStatus() == MessageStatus.FAILED) { // Real failure — surface a retry action } } @@ -160,7 +161,8 @@ public void onError(CometChatException e) { ```kotlin override fun onError(e: CometChatException?) { - when (message.status) { + // textMessage is the message you passed to sendMessage; the SDK updates its status in place. + when (textMessage.status) { MessageStatus.PENDING -> { /* Queued offline — auto-sends on reconnect */ } MessageStatus.FAILED -> { /* Real failure — surface a retry action */ } else -> {} @@ -180,18 +182,18 @@ Every message exposes its offline state. `getLocalMessageId()` returns the SDK-g |---|---|---| | `getLocalMessageId()` | `String` | Client handle; present while offline is running. | | `getStatus()` | `MessageStatus` | `PENDING`, `SENDING`, `SENT`, `DELIVERED`, `READ`, `FAILED`. | -| `getFailureReason()` | `MessageFailureReason` | Set only while `FAILED`; otherwise empty. | +| `getFailureReason()` | `MessageFailureReason` | Set only while `FAILED`; otherwise `null`. | `MessageFailureReason` values: | Reason | When | |---|---| -| `TRANSIENT` | A temporary server error (for example 5xx or rate-limited), or no answer after the auto-retry cap. Retryable. | +| `TRANSIENT` | A temporary server error (5xx, or 429 rate-limited), no answer after 5 automatic reconnect attempts, or, with `autoSendPendingOnReconnect` off, no answer at all. Retryable. | | `PERMANENT` | The server rejected the message, or the error was not recognized. A manual retry will not help. | | `QUEUE_FULL` | A new send was refused because the queue is full (count cap). | | `QUEUE_OVERFLOW` | An older queued message was evicted to admit a newer one (`DROP_OLDEST` only). | -| `BYTE_OVERFLOW` | A media send was refused because it exceeds the queue's byte budget. | -| `DISCARDED` | The message was removed before it could send (for example `discardLocalMessage`, or a cache clear). | +| `BYTE_OVERFLOW` | A media send was refused because caching its bytes would exceed `maxMediaCacheSize`, the budget for the whole media cache (received and queued media). | +| `DISCARDED` | A queued message was removed before it could send: by `clearOfflineCache`, by leaving or deleting its conversation, or by logout. Reported with code `ERROR_OFFLINE_MESSAGE_DISCARDED`. (`discardLocalMessage` reports `onSuccess` instead.) | ## Inspect the Queue @@ -280,7 +282,7 @@ CometChat.removeOfflineMessageListener("LISTENER_ID") ## Retry and Discard -There is no automatic retry of a `FAILED` message — recovery is explicit and user-invoked. Use the message's `localMessageId`. +There is no automatic retry of a `FAILED` message — recovery is explicit and user-invoked. A `FAILED` message stays in the outbox until you retry or discard it. Use the message's `localMessageId`. - **`retryMessage(localMessageId, listener)`** re-attempts one message. A `FAILED` message is reset to `PENDING` and sent; a `PENDING` message is attempted now rather than waiting for a reconnect. The listener receives that attempt's outcome. - **`retryAllFailedMessages(listener)`** retries **transient** failures only; permanent ones are skipped. @@ -335,7 +337,7 @@ CometChat.discardLocalMessage(localMessageId, object : CometChat.CallbackListene ## Auto-send on Reconnect -By default, queued `PENDING` messages are sent automatically when the realtime connection is re-established. This is controlled by `OfflineConfig.autoSendPendingOnReconnect` (default `true`); see [Enable Offline Support](/sdk/android/v5/offline-enable) and the [reference](/sdk/android/v5/offline-reference). With it off, nothing retries a queued message on reconnect — a send that gets no answer becomes `FAILED` (`TRANSIENT`), which you recover with `retryMessage`. +By default, queued `PENDING` messages are sent automatically when the realtime connection is re-established. This is controlled by `OfflineConfig.autoSendPendingOnReconnect` (default `true`); see [Enable Offline Support](/sdk/android/v5/offline-enable) and the [reference](/sdk/android/v5/offline-reference). A queued message that keeps getting no answer is retried on each reconnect, up to 5 times, then becomes `FAILED` (`TRANSIENT`). With it off, nothing retries a queued message on reconnect — a send that gets no answer becomes `FAILED` (`TRANSIENT`), which you recover with `retryMessage`. Reconnect also runs a catch-up sync for received messages — see [Stay in Sync](/sdk/android/v5/offline-sync). @@ -346,15 +348,15 @@ The outbox holds at most `maxPendingMessages` messages still trying to send (`PE | `PendingMessageOverflowPolicy` | Behaviour on overflow | |---|---| | `REJECT_NEWEST` (**default**) | The new send is refused and reported with reason `QUEUE_FULL`. Nothing already queued is lost. | -| `DROP_OLDEST` | The oldest `PENDING` message is evicted (reason `QUEUE_OVERFLOW`) to admit the new one. | +| `DROP_OLDEST` | The oldest `PENDING` message is evicted to admit the new one, and reported with `onError` (code `ERROR_PENDING_QUEUE_LIMIT`, reason `QUEUE_OVERFLOW`). If there is no `PENDING` message to evict, the new send is refused with `QUEUE_FULL`. | -A media send that exceeds the queue's byte budget is refused with reason `BYTE_OVERFLOW`. +A media send whose bytes would push the media cache over `maxMediaCacheSize` is refused with reason `BYTE_OVERFLOW`; nothing is evicted to make room. A **refused** send (`QUEUE_FULL` / `BYTE_OVERFLOW`) is never queued, so it fires **no** `onMessagePending`; it reports `onError` with the code `ERROR_PENDING_QUEUE_LIMIT`, and the global `OfflineMessageListener` receives `onOutboxMessageFailed(message, reason)` carrying the exact reason. -A refused or evicted message (`QUEUE_FULL`, `QUEUE_OVERFLOW`, `BYTE_OVERFLOW`) has **no durable row** — it does not appear in `getPendingMessages`, and calling `retryMessage` on its id does nothing. Capture these from the failure callback and handle them at that moment. Prefer the default `REJECT_NEWEST`, which never silently drops an already-queued message from another conversation. +A refused or evicted message (`QUEUE_FULL`, `QUEUE_OVERFLOW`, `BYTE_OVERFLOW`) has **no durable row** — it does not appear in `getPendingMessages`, and calling `retryMessage` on its id fails with `ERROR_OFFLINE_MESSAGE_NOT_FOUND`. Capture these from the failure callback and handle them at that moment. Prefer the default `REJECT_NEWEST`, which never silently drops an already-queued message from another conversation. @@ -364,7 +366,7 @@ Offline queuing applies to **sending new messages** only. Changes to existing me ## Durable Media Uploads -A multi-attachment upload survives an app restart: the SDK re-attaches to an in-progress batch instead of restarting it. Fetch the existing request for a conversation with `getExistingUploadFileRequest`, and read each file's progress through `getItems()`, which returns a list of `UploadFileItemStatus` (each with a `State`: `QUEUED`, `UPLOADING`, `UPLOADED`, `FAILED`, or `REJECTED`, which cannot be retried). Retry an individual file with `retryAttachment(fileId)`. +A multi-attachment upload survives an app restart, but it never resumes on its own. Fetch the existing request for a conversation with `getExistingUploadFileRequest`, and read each file's progress through `getItems()`, which returns a list of `UploadFileItemStatus` (each with a `State`: `QUEUED`, `UPLOADING`, `UPLOADED`, `FAILED`, or `REJECTED`, which cannot be retried). Retry an individual file with `retryAttachment(fileId)`. @@ -389,10 +391,12 @@ if (request != null) { val request = CometChat.getExistingUploadFileRequest("UID", CometChatConstants.RECEIVER_TYPE_USER) -request?.items?.forEach { item -> - Log.d(TAG, "${item.fileId} -> ${item.state}") - if (item.state == UploadFileItemStatus.State.FAILED) { - request.retryAttachment(item.fileId) +if (request != null) { + for (item in request.items) { + Log.d(TAG, "${item.fileId} -> ${item.state}") + if (item.state == UploadFileItemStatus.State.FAILED) { + request.retryAttachment(item.fileId) + } } } ``` From bc5aad28e623fac05741646f56f034128083925e Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:47:44 +0530 Subject: [PATCH 14/42] =?UTF-8?q?docs(android-v5):=20stay=20in=20sync=20?= =?UTF-8?q?=E2=80=94=20when=20sync=20runs,=20conversation=20removal,=20typ?= =?UTF-8?q?ed=20sync=20callbacks,=20cache=20lifetime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-sync.mdx | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/sdk/android/v5/offline-sync.mdx b/sdk/android/v5/offline-sync.mdx index 460d61d9a..8fe482dd3 100644 --- a/sdk/android/v5/offline-sync.mdx +++ b/sdk/android/v5/offline-sync.mdx @@ -48,12 +48,12 @@ Everything on this page is a no-op unless offline support is active for the logg ## How catch-up works -A sync runs automatically when the connection settles after a reconnect. It never runs on a brief connect-then-drop flap, and overlapping triggers coalesce into a single sweep. It proceeds in two phases, in order: +A sync runs automatically whenever the connection settles, including the first connect after login or launch. It never runs on a brief connect-then-drop flap, and overlapping triggers coalesce: at most one follow-up sweep is queued behind the running one. It proceeds in two phases, in order: 1. **Conversation phase** — the conversation list is fetched as one fresh page. A conversation-list UI can blind-replace its list with the result. 2. **Message phase** — the SDK replays the message changelog from the last sync point. New messages and updates to already-cached messages are merged into the cache. -The sync is incremental and safe to repeat: it only moves forward from the last successful point, it never deletes cached messages, and a failed or interrupted phase simply resumes on the next reconnect. A first-ever sync (cold start) seeds its starting point from the newest conversation and fetches no message history — older history is reached by scrolling online. +The sync is incremental and safe to repeat: it only moves forward from the last successful point, and a failed or interrupted phase simply resumes on the next reconnect. The conversation refresh does remove conversations you are no longer part of, along with their cached messages, media, and any still-queued messages for them (which fail with reason `DISCARDED`). A first-ever sync (cold start) seeds its starting point from the newest conversation and fetches no message history — older history is reached by scrolling online. ## Listen for sync progress @@ -117,7 +117,7 @@ CometChat.addSyncListener("SYNC_LISTENER", object : CometChat.SyncListener() { |---|---| | `onConversationSyncStarted()` | The conversation refresh began. | | `onConversationSyncCompleted(List fetched)` | The refresh succeeded; `fetched` is the full conversation page. | -| `onConversationSyncError(CometChatException)` | A transport or parse failure; the cache is left untouched and the refresh retries on the next reconnect. | +| `onConversationSyncError(CometChatException)` | The refresh failed. A transport failure leaves the cache untouched; a malformed response still merges the rows it could parse but removes nothing. Either way the refresh retries on the next reconnect. | | `onMessageSyncStarted()` | The message changelog sweep began. Never fires on a cold start. | | `onMessageSyncCompleted(List newPlusUpdates, SyncOutcome)` | The sweep finished; `newPlusUpdates` carries the new and changed messages. | | `onMessageSyncError(CometChatException)` | The sweep failed; it resumes on the next reconnect. | @@ -131,7 +131,7 @@ The `SyncListener` is self-sufficient: `onMessageSyncCompleted` carries the swep | `SyncOutcome` | Meaning | |---|---| | `CAUGHT_UP` | The changelog was fully replayed; the cache reflects every server change up to the sync point. | -| `PARTIAL` | A page was ingested and the sync point advanced, but a backlog remains; it drains one page per reconnect. | +| `PARTIAL` | A page was ingested and the sync point advanced, but a backlog remains. Each sync, automatic or from `forceSyncNow`, drains one page of up to 1000 changes. | Remove the listener with `CometChat.removeSyncListener("SYNC_LISTENER")`. @@ -139,7 +139,7 @@ Remove the listener with `CometChat.removeSyncListener("SYNC_LISTENER")`. If you already use a `CometChat.MessageListener`, synced messages still arrive there, so you usually do not need a `SyncListener` to keep your chat up to date: -- **Genuinely new** messages arrive one-by-one on `onMessageReceived` (and the typed variants), tagged with source `SYNC`. +- **Genuinely new** messages arrive one-by-one on the typed callbacks (`onTextMessageReceived`, `onMediaMessageReceived`, `onCustomMessageReceived`, …), each tagged with source `SYNC`. - **Updates** to already-cached messages arrive together on `onMessagesSynced(List)`. @@ -147,12 +147,14 @@ If you already use a `CometChat.MessageListener`, synced messages still arrive t ```java CometChat.addMessageListener("MSG_LISTENER", new CometChat.MessageListener() { @Override - public void onMessageReceived(BaseMessage message) { + public void onTextMessageReceived(TextMessage message) { if (MessageSource.SYNC == message.getSource()) { // Delivered by a catch-up sync. Suppress notification spam if you wish. } } + // Do the same in onMediaMessageReceived, onCustomMessageReceived and the other typed callbacks. + @Override public void onMessagesSynced(List messages) { // Updates to already-cached messages. Merge by id. @@ -165,12 +167,14 @@ CometChat.addMessageListener("MSG_LISTENER", new CometChat.MessageListener() { ```kotlin CometChat.addMessageListener("MSG_LISTENER", object : CometChat.MessageListener() { - override fun onMessageReceived(message: BaseMessage) { + override fun onTextMessageReceived(message: TextMessage) { if (MessageSource.SYNC == message.source) { // Delivered by a catch-up sync. Suppress notification spam if you wish. } } + // Do the same in onMediaMessageReceived, onCustomMessageReceived and the other typed callbacks. + override fun onMessagesSynced(messages: List) { // Updates to already-cached messages. Merge by id. } @@ -180,6 +184,10 @@ CometChat.addMessageListener("MSG_LISTENER", object : CometChat.MessageListener( + +On reconnect, queued messages are sent first, then the sync runs. `forceSyncNow` runs only the sync; it does not send queued messages (use `retryMessage` or `retryAllFailedMessages` for those). A message deleted on the server reaches you as an update on `onMessagesSynced`, not as a removal. + + ## Check sync state Poll the current sync state and the time of the last completed sync at any time. Both are safe to call on the main thread. @@ -320,12 +328,12 @@ CometChat.clearOfflineCache() // wipe the entire per-user cache -`clearOfflineCache()` is destructive and irreversible. It empties the entire per-user cache — cached messages, conversations, media bytes **and the un-sent outbox**. Each still-queued `PENDING` / `SENDING` message fails with reason `DISCARDED`. It is a no-op unless offline support is active. +`clearOfflineCache()` is destructive and irreversible. It empties the entire per-user cache — cached messages, conversations, media bytes **and the un-sent outbox**. Each still-queued `PENDING` / `SENDING` message fails with reason `DISCARDED`; messages that had already failed are removed without a further callback. It is a no-op unless offline support is active. -You never need to clear the cache on logout — the SDK does it for you. The cache is stored **per user**: it is kept across re-login as the same user, and wiped automatically on logout, token expiry, or when a different user logs in. See [Offline Support](/sdk/android/v5/offline-overview). +You never need to clear the cache on logout — the SDK does it for you. The cache is stored **per user**: it survives app restarts and a token refresh for the same logged-in user, and is wiped automatically on logout, when the server rejects the auth token, or when a different user or app ID is used. See [Offline Support](/sdk/android/v5/offline-overview). From 5a542499b59f1758b63e3ff156cf94164de610ee Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:51:27 +0530 Subject: [PATCH 15/42] =?UTF-8?q?docs(android-v5):=20media=20caching=20?= =?UTF-8?q?=E2=80=94=20download=20error=20codes,=20re-sign=20conditions,?= =?UTF-8?q?=20cache=20budget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-media-caching.mdx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sdk/android/v5/offline-media-caching.mdx b/sdk/android/v5/offline-media-caching.mdx index fe089656d..581fdd789 100644 --- a/sdk/android/v5/offline-media-caching.mdx +++ b/sdk/android/v5/offline-media-caching.mdx @@ -149,7 +149,7 @@ Each attachment of a multi-attachment message resolves independently. An attachm ## Downloading on demand -Call `downloadMedia` to fetch and cache an attachment's bytes — for example when the user taps a placeholder. The SDK runs the resolution off the main thread (cached bytes, then a valid URL, then a re-signed URL if the presigned one expired) and delivers on the main thread. +Call `downloadMedia` to fetch and cache an attachment's bytes — for example when the user taps a placeholder. The SDK runs the resolution off the main thread (cached bytes, then a valid URL, then, for a CometChat-hosted URL the server rejects as expired or forbidden (HTTP 401, 403 or 410), one re-signed URL) and delivers on the main thread. @@ -192,6 +192,14 @@ CometChat.downloadMedia(attachment, object : MediaCallbackListener { `onProgress(int percent)` reports `0..100`, best-effort and monotonic non-decreasing. It is emitted only when the server reports a content length; for an unknown-length download it is skipped and the call goes straight to `onSuccess`. Progress never blocks or fails the download. +`onError` receives one of these codes: + +| Error code | Meaning | +|---|---| +| `ERROR_INVALID_FILE_URL` | The attachment has no URL. | +| `ERROR_MEDIA_UNAVAILABLE` | The bytes are not cached and could not be fetched (for example, the device is offline), or offline support is not active. | +| `ERROR_MEDIA_DOWNLOAD_FAILED` | The download started but failed. | + ## Observe background downloads In `AUTO` mode, incoming media downloads in the background with no per-call callback. Register a `MediaDownloadListener` to learn when the bytes land (or fail) so a UI tile can refresh. All callbacks fire on the main thread. @@ -234,6 +242,6 @@ Remove the listener with `CometChat.removeMediaDownloadListener("MEDIA_LISTENER" ## Media cache size limit -Cached media bytes are bounded by `maxMediaCacheSize` (default **500 MB**; `0` means unlimited). When the cache exceeds the limit, the least recently accessed media is deleted first until it is back under budget. Media belonging to un-sent messages and in-progress uploads is never evicted and does not count toward the budget. +Cached media bytes are bounded by `maxMediaCacheSize` (default **500 MB**; `0` means unlimited). When the cache exceeds the limit, the least recently accessed media is deleted first until it is back under budget. Media for un-sent (queued) messages is never evicted, though it still counts toward the budget; copies held for an in-progress upload are excluded from the budget. Because protected media is never evicted, the cache can briefly exceed `maxMediaCacheSize`. Bulk list loads and syncs do not refresh an item's recency. Media is stored as files in durable, backup-excluded storage; the location is not configurable. Eviction runs silently in the background — there is no cache-limit callback. To reason about size, use `CometChat.getCacheSize()` and the configured limits. See the [configuration reference](/sdk/android/v5/offline-reference) for the full set of options. From 882a2a4e1b0f3bc908558faac1b41f0d55e81350 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:54:04 +0530 Subject: [PATCH 16/42] =?UTF-8?q?docs(android-v5):=20encrypt=20the=20cache?= =?UTF-8?q?=20=E2=80=94=20random=20key,=20unsent=20messages=20on=20reset,?= =?UTF-8?q?=20ESCALATED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-encryption.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/offline-encryption.mdx b/sdk/android/v5/offline-encryption.mdx index a1ca00965..043d783b2 100644 --- a/sdk/android/v5/offline-encryption.mdx +++ b/sdk/android/v5/offline-encryption.mdx @@ -92,7 +92,7 @@ val offlineConfig = OfflineConfig.Builder() ## Key storage -You do not supply or manage the encryption key. The SDK derives a per-user key (scoped to the app ID and the logged-in user's UID), wraps it with a non-exportable **Android Keystore** key, and stores the wrapped blob in the app's no-backup files directory. The key never leaves the device and is never returned by any API. If the key is lost or the stored database no longer matches it, the cache resets and rebuilds — see [Cache resets](#cache-resets-on-key-loss). +You do not supply or manage the encryption key. The SDK generates a random 32-byte key, one per app ID and logged-in user (UID), wraps it with a non-exportable **Android Keystore** key, and stores the wrapped blob in the app's no-backup files directory. The key never leaves the device and is never returned by any API. If the key is lost or the stored database no longer matches it, the cache resets and rebuilds — see [Cache resets](#cache-resets-on-key-loss). ## Check encryption status @@ -146,7 +146,7 @@ Media bytes live outside the database because `Attachment.getLocalPath()` is pub ## Cache resets on key loss -If the key can no longer decrypt the stored database, the SDK wipes the local cache and rebuilds it **encrypted** — it never silently falls back to plaintext for a cache that could be encrypted. Any unsent messages still queued in the outbox at that point are lost and each is failed through its send listener. +If the key can no longer decrypt the stored database, the SDK wipes the local cache and rebuilds it **encrypted** — it never silently falls back to plaintext for a cache that could be encrypted. Any unsent messages still queued in the outbox at that point are lost. They lived inside the unreadable database, so they cannot be counted or reported one by one: `pendingMessagesLost` is `OfflineCacheResetListener.PENDING_COUNT_UNKNOWN` (`-1`). When the reset is `ENCRYPTION_CHANGED` instead, each queued message fails with reason `DISCARDED` and `pendingMessagesLost` is the exact count, unless the old database could not be read. Register an `OfflineCacheResetListener` to be told when this happens: @@ -156,6 +156,7 @@ Register an `OfflineCacheResetListener` to be told when this happens: CometChat.addOfflineCacheResetListener("LISTENER_ID", new OfflineCacheResetListener() { @Override public void onOfflineCacheReset(OfflineCacheResetReason reason, int pendingMessagesLost) { + // pendingMessagesLost is -1 (PENDING_COUNT_UNKNOWN) when the number lost is unknown, not zero. Log.w(TAG, "Cache reset: " + reason + ", pending lost=" + pendingMessagesLost); } }); @@ -166,6 +167,7 @@ CometChat.addOfflineCacheResetListener("LISTENER_ID", new OfflineCacheResetListe ```kotlin CometChat.addOfflineCacheResetListener("LISTENER_ID", object : OfflineCacheResetListener() { override fun onOfflineCacheReset(reason: OfflineCacheResetReason, pendingMessagesLost: Int) { + // pendingMessagesLost is -1 (PENDING_COUNT_UNKNOWN) when the number lost is unknown, not zero. Log.w(TAG, "Cache reset: $reason, pending lost=$pendingMessagesLost") } }) @@ -173,4 +175,4 @@ CometChat.addOfflineCacheResetListener("LISTENER_ID", object : OfflineCacheReset -The encryption-related reasons are `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, and `ENCRYPTION_CHANGED` (you turned encryption on or off between launches). See [Stay in Sync](/sdk/android/v5/offline-sync) for the full cache-reset flow and the complete list of reasons, and the [API Reference](/sdk/android/v5/offline-reference) for every field and enum. +The encryption-related reasons are `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, `ESCALATED`, and `ENCRYPTION_CHANGED` (you turned encryption on or off between launches, which also discards any queued messages and the cached media). See [Stay in Sync](/sdk/android/v5/offline-sync) for the full cache-reset flow and the complete list of reasons, and the [API Reference](/sdk/android/v5/offline-reference) for every field and enum. From e4bcbe653c603fa8279dd3a571a45b73156e9cf8 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:56:14 +0530 Subject: [PATCH 17/42] =?UTF-8?q?docs(android-v5):=20offline=20reference?= =?UTF-8?q?=20=E2=80=94=20missing=20APIs=20and=20error=20codes,=20clamp=20?= =?UTF-8?q?rules,=20forceSyncNow,=20drop=20debugTracing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-reference.mdx | 32 +++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/sdk/android/v5/offline-reference.mdx b/sdk/android/v5/offline-reference.mdx index c3f027d0e..2527394ab 100644 --- a/sdk/android/v5/offline-reference.mdx +++ b/sdk/android/v5/offline-reference.mdx @@ -36,25 +36,24 @@ This page is the full surface of CometChat's offline support on Android: configu ## OfflineConfig options -Build with `OfflineConfig.Builder`. Every field is pre-set to its default, so call only the setters you want to change; the resulting `OfflineConfig` is immutable. Byte caps and the TTL treat `0` as **unlimited** (that dimension's eviction is disabled); the count caps are clamped and have no "unlimited". +Build with `OfflineConfig.Builder`. Every field is pre-set to its default, so call only the setters you want to change; the resulting `OfflineConfig` is immutable. Byte caps and the TTL treat `<= 0` as **unlimited** (that dimension's eviction is disabled); the count caps are clamped and have no "unlimited". Each default and upper limit is also available as a constant on `OfflineConfig` (`DEFAULT_MAX_CONVERSATIONS`, `MAX_PENDING_MESSAGES`, and so on). | Builder setter | Type | Default | Range / clamp | Effect | |---|---|---|---|---| | `enabled(boolean)` | `boolean` | `false` | — | Master gate for the whole offline engine. With it off the SDK behaves exactly as before. | | `cacheDurationDays(int)` | `int` | `0` (unlimited) | `>= 0`; negative treated as `0` | Message retention TTL in days. `0` = no age-based prune; eviction is by the byte/count caps instead. | -| `maxStorageSize(long)` | `long` bytes | `262144000` (250 MB) | `0` = unlimited | Byte ceiling for the message/metadata database. | -| `maxMessagesPerConversation(int)` | `int` | `500` | clamped `[1, 1000]`; `0`/over → `1000` | Per-conversation cached-message window. | -| `maxConversations(int)` | `int` | `100` | clamped `[1, 1000]`; `0`/over → `1000` | Cached-conversation cap. | -| `maxPendingMessages(int)` | `int` | `50` | clamped `[1, 100]`; `0`/over → `100` | Pending-send outbox capacity. | +| `maxStorageSize(long)` | `long` bytes | `262144000` (250 MB) | `<= 0` = unlimited | Byte ceiling for the message/metadata database. | +| `maxMessagesPerConversation(int)` | `int` | `500` | clamped `[1, 1000]`; `<= 0` or over → `1000` | Per-conversation cached-message window. | +| `maxConversations(int)` | `int` | `100` | clamped `[1, 1000]`; `<= 0` or over → `1000` | Cached-conversation cap. | +| `maxPendingMessages(int)` | `int` | `50` | clamped `[1, 100]`; `<= 0` or over → `100` | Pending-send outbox capacity. | | `autoSendPendingOnReconnect(boolean)` | `boolean` | `true` | — | Auto-flush queued `PENDING` sends on reconnect. Gates only the outbox flush; the reconnect sync sweep runs regardless. | | `pendingQueueOverflowPolicy(PendingMessageOverflowPolicy)` | enum | `REJECT_NEWEST` | — | What the outbox does when it is full. | -| `maxMediaCacheSize(long)` | `long` bytes | `524288000` (500 MB) | `0` = unlimited | Media-only byte budget (LRU). | +| `maxMediaCacheSize(long)` | `long` bytes | `524288000` (500 MB) | `<= 0` = unlimited | Media-only byte budget (LRU). | | `mediaCaching(MediaCachingMode)` | enum | `AUTO` | `null` → `AUTO` | Receive-side media mode. Fixed at init. | -| `encryption(boolean)` | `boolean` | `false` | — | Encrypt the cache database at rest. See [Encrypt the Cache](/sdk/android/v5/offline-encryption). | +| `encryption(boolean)` | `boolean` | `false` | — | Encrypt the cache database at rest. Requires the `net.zetetic:sqlcipher-android` dependency in your app; without it, `encryptionFailureBehavior` decides what happens. See [Encrypt the Cache](/sdk/android/v5/offline-encryption). | | `encryptionFailureBehavior(EncryptionFailureBehavior)` | enum | `ONLINE_ONLY` | `null` → `ONLINE_ONLY` | What happens when encryption cannot run at all. | | `maxSnapshotQueries(int)` | `int` | `100` | `>= 1`; `0`/negative → `100` | Most entity-list snapshots (users, groups, group members) kept for offline replay; LRU. | | `snapshotTtlDays(int)` | `int` | `7` | `>= 1`; `0`/negative → `7` | Days an entity-list snapshot is kept after it was first recorded. | -| `debugTracing(boolean)` | `boolean` | `false` | — | Dev-only file trace of offline operations. Leave off in production. | Attach the config at init via `AppSettings.AppSettingsBuilder.setOfflineConfig(OfflineConfig)`, or enable with defaults via the `offline(boolean)` shorthand — see [Enable Offline Support](/sdk/android/v5/offline-enable). @@ -64,12 +63,12 @@ Attach the config at init via `AppSettings.AppSettingsBuilder.setOfflineConfig(O |---|---|---| | `isOfflineEnabled()` | `boolean` | Whether offline support is active for the current session. | | `getMediaCachingMode()` | `MediaCachingMode` | The effective media caching mode (`OFF` whenever offline is not active). | -| `getOfflineEncryptionStatus()` | `OfflineEncryptionStatus` | What encryption at rest is actually doing. | +| `getOfflineEncryptionStatus()` | `OfflineEncryptionStatus` | What encryption at rest is actually doing. Compare its strings against the `OfflineEncryptionStatus.AT_REST_*` and `KEY_STORAGE_*` constants. | | `getPendingMessages(CallbackListener>)` | `void` | All queued/failed messages in the outbox. | | `retryMessage(String localMessageId, MessageSentListener)` | `void` | Retry one failed message by its local id. | | `retryAllFailedMessages(MessageSentListener)` | `void` | Retry every failed message in the outbox. | | `discardLocalMessage(String localMessageId, CallbackListener)` | `void` | Remove one unsent message from the outbox. | -| `forceSyncNow()` | `void` | Trigger a catch-up sync immediately. No-op when offline is not active. | +| `forceSyncNow()` | `void` | Request a catch-up sync. Silently dropped if a sync is already running or one ran within the cooldown window, and a no-op when offline is not active. Use the callback overload to learn why a sync did not start. | | `forceSyncNow(CallbackListener)` | `void` | Same, with a callback that reports why a manual sync did not start. | | `getSyncState()` | `SyncState` | Whether a catch-up sweep is currently running. | | `getLastSyncTimestamp()` | `long` | Server time of the last successful sync, in epoch seconds. | @@ -80,6 +79,8 @@ Attach the config at init via `AppSettings.AppSettingsBuilder.setOfflineConfig(O | `getExistingUploadFileRequest(String receiverId, String receiverType)` | `UploadFileRequest` | Re-attach to an in-progress offline upload. | | `getExistingUploadFileRequest(String receiverId, String receiverType, long parentMessageId)` | `UploadFileRequest` | Same, for a thread parent. | +On the returned `UploadFileRequest`, `getItems()` lists each file's `UploadFileItemStatus`, and `retryAttachment(String fileId)` (optionally with an `UploadFileListener`) retries one failed file. New batches are created with `CometChat.createUploadFileRequest`; see [Upload Files](/sdk/android/v5/upload-files). + ### Listener registration | Method | Listener | @@ -117,7 +118,7 @@ Each delivers the cached page first (when offline is active and a user is logged | `MessageSentListener` | `onMessagePending(BaseMessage)`, `onMessageSent(BaseMessage)`, `onError(CometChatException)` | | `OfflineMessageListener` | `onOutboxMessagePending(BaseMessage)`, `onOutboxMessageSent(BaseMessage)`, `onOutboxMessageFailed(BaseMessage, MessageFailureReason)` | | `SyncListener` | `onConversationSyncStarted()`, `onConversationSyncCompleted(List)`, `onConversationSyncError(CometChatException)`, `onMessageSyncStarted()`, `onMessageSyncCompleted(List, SyncOutcome)`, `onMessageSyncError(CometChatException)` | -| `OfflineCacheResetListener` | `onOfflineCacheReset(OfflineCacheResetReason, int pendingMessagesLost)` | +| `OfflineCacheResetListener` | `onOfflineCacheReset(OfflineCacheResetReason, int pendingMessagesLost)`; `pendingMessagesLost` is `OfflineCacheResetListener.PENDING_COUNT_UNKNOWN` (`-1`) when the number is unknown | | `MediaCallbackListener` | `onSuccess(String localPath)`, `onProgress(int percent)`, `onError(CometChatException)` | | `MediaDownloadListener` | `onMediaDownloaded(String messageLocalId, String attachmentId, String localPath)`, `onMediaDownloadFailed(String messageLocalId, String attachmentId, CometChatException)` | | `MessageListener` | `onMessagesSynced(List)` (offline addition to the existing listener) | @@ -142,11 +143,12 @@ Each delivers the cached page first (when offline is active and a user is logged |---|---|---| | `BaseMessage.getLocalMessageId()` | `String` | SDK-generated local handle for an optimistic/offline send; the stable key to reconcile the row when the server ack arrives. `null` for messages that never went through the outbox. | | `BaseMessage.getStatus()` | `MessageStatus` | Lifecycle status of the message. | +| `BaseMessage.getFailureReason()` | `MessageFailureReason` | Why the message failed; set only while `FAILED`, otherwise `null`. | | `Attachment.getLocalPath()` | `String` | Absolute device path to cached media bytes, or `null` when not cached. Never sent to the server. | ## Error codes -Compare `CometChatException.getCode()` against these codes. All are constants in `CometChatConstants.Errors` except `ERROR_PENDING_QUEUE_LIMIT` and `ERROR_OFFLINE_MESSAGE_PERSIST_FAILED`, which you compare as string values. +Compare `CometChatException.getCode()` against these codes. All are constants in `CometChatConstants.Errors` except `ERROR_PENDING_QUEUE_LIMIT`, `ERROR_OFFLINE_MESSAGE_PERSIST_FAILED`, `ERROR_OFFLINE_MEDIA_COPY_FAILED`, `ERROR_MEDIA_UNAVAILABLE` and `ERROR_MEDIA_DOWNLOAD_FAILED`, which you compare as string values. | Code | Raised when | |---|---| @@ -159,6 +161,12 @@ Compare `CometChatException.getCode()` against these codes. All are constants in | `ERROR_OFFLINE_MESSAGE_NOT_FOUND` | `retryMessage` / `discardLocalMessage` found no unsent message with the given local id. | | `ERROR_OFFLINE_MESSAGE_PERSIST_FAILED` | The local cache was briefly unavailable, so `getPendingMessages`, `retryMessage`, `retryAllFailedMessages` or `discardLocalMessage` changed nothing. Try again. | | `ERROR_PENDING_QUEUE_LIMIT` | A send was refused because the outbox is full or the media is too large; the reason is `QUEUE_FULL` or `BYTE_OVERFLOW`. | +| `ERROR_OFFLINE_MEDIA_COPY_FAILED` | A media send could not be queued because its file could not be copied; the reason is `PERMANENT`. | +| `ERROR_USER_NOT_LOGGED_IN` | An offline-only call was made with no logged-in user. | +| `ERROR_REQUEST_IN_PROGRESS` | A fetch was started while an earlier fetch on the same request object is still running. | +| `ERROR_INVALID_FILE_URL` | `downloadMedia` was called for an attachment with no URL. | +| `ERROR_MEDIA_UNAVAILABLE` | `downloadMedia` found no cached bytes and could not fetch them, or offline support is not active. | +| `ERROR_MEDIA_DOWNLOAD_FAILED` | A `downloadMedia` download started but failed. | ## Deprecated overloads and replacements From 789c2ce624db7c22dfbfa9e2de7cca21cf1d61b0 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 17:59:06 +0530 Subject: [PATCH 18/42] =?UTF-8?q?docs(android-v5):=20shared=20deprecation?= =?UTF-8?q?=20callouts=20=E2=80=94=20offline=20onError=20notice,=20old=20o?= =?UTF-8?q?verloads=20never=20queued,=205.1.0=20requirement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- snippets/sdk/offline-deprecated-listener.mdx | 2 +- snippets/sdk/offline-deprecated-send.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/snippets/sdk/offline-deprecated-listener.mdx b/snippets/sdk/offline-deprecated-listener.mdx index 7c496516b..b53badced 100644 --- a/snippets/sdk/offline-deprecated-listener.mdx +++ b/snippets/sdk/offline-deprecated-listener.mdx @@ -1,3 +1,3 @@ -**Starting with v5.1.0, the `CallbackListener` overload of {method} is deprecated.** Use {replacement} instead. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the server result arrives in the `onServer…` callback instead of `onSuccess`. With offline support on, it also delivers the cached result first. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +**Starting with v5.1.0, the `CallbackListener` overload of {method} is deprecated.** Use {replacement} instead; it requires SDK 5.1.0 or later, so on 5.0.x keep using `CallbackListener`. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the server result arrives in the `onServer…` callback instead of `onSuccess`. With offline support on, it also delivers the cached result first; the old overload keeps working but receives only the server result. See [Show Cached Data](/sdk/android/v5/offline-cached-data). diff --git a/snippets/sdk/offline-deprecated-send.mdx b/snippets/sdk/offline-deprecated-send.mdx index b020c23b3..0b9a21797 100644 --- a/snippets/sdk/offline-deprecated-send.mdx +++ b/snippets/sdk/offline-deprecated-send.mdx @@ -1,3 +1,3 @@ -**Starting with v5.1.0, the `CallbackListener` overloads of {method} are deprecated.** Use `MessageSentListener` instead; it requires SDK 5.1.0 or later. On 5.0.x, keep using `CallbackListener` and handle the sent message in `onSuccess` where these examples use `onMessageSent`. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the sent message arrives in `onMessageSent` instead of `onSuccess`. With offline support on, `onMessagePending` fires first, and a message sent without a connection is queued and sent when the connection returns. See [Send While Offline](/sdk/android/v5/offline-send). +**Starting with v5.1.0, the `CallbackListener` overloads of {method} are deprecated.** Use `MessageSentListener` instead; it requires SDK 5.1.0 or later. On 5.0.x, keep using `CallbackListener` and handle the sent message in `onSuccess` where these examples use `onMessageSent`. With offline support off, it behaves exactly like the old callback: `onError` is unchanged, and the sent message arrives in `onMessageSent` instead of `onSuccess`. With offline support on, `onMessagePending` fires first. A message sent without a connection stays `PENDING` and is sent when the connection returns: `onError` fires with code `ERROR_INTERNET_UNAVAILABLE` as a not-connected notice, and `onMessageSent` follows once the message is sent. In `onError`, check the message's `getStatus()`: `PENDING` means queued, `FAILED` means it failed. The old `CallbackListener` overloads are never queued; offline, they end with `onError`. See [Send While Offline](/sdk/android/v5/offline-send). From 509caa76ddaaa688c577301470ecf7c4bad43f02 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:00:41 +0530 Subject: [PATCH 19/42] =?UTF-8?q?docs(android-v5):=20connection=20status?= =?UTF-8?q?=20=E2=80=94=20only=20MessageSentListener=20sends=20are=20queue?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/connection-status.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/android/v5/connection-status.mdx b/sdk/android/v5/connection-status.mdx index 76fc89523..65fef0935 100644 --- a/sdk/android/v5/connection-status.mdx +++ b/sdk/android/v5/connection-status.mdx @@ -8,7 +8,7 @@ description: "Track whether your Android app is connecting, connected or disconn CometChat SDK provides you with a mechanism to get real-time status of the connection to CometChat web-socket servers. To achieve this you need to use the `ConnectionListener` class provided by the CometChat SDK -With offline support enabled, your app keeps working without a connection: cached chats are shown instantly, and messages you send are queued and delivered automatically on reconnect. See [Show Cached Data](/sdk/android/v5/offline-cached-data) and [Send While Offline](/sdk/android/v5/offline-send). +With offline support enabled, your app keeps working without a connection: cached chats are shown instantly, and messages you send with a `MessageSentListener` are queued and delivered automatically on reconnect. See [Show Cached Data](/sdk/android/v5/offline-cached-data) and [Send While Offline](/sdk/android/v5/offline-send). Connection Status provides you with the below 3 methods to get the status of the connection to CometChat web-socket servers: From 81ec6e520e3c0bfaff9dc85efde4132553a0ea9e Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:02:36 +0530 Subject: [PATCH 20/42] =?UTF-8?q?docs(android-v5):=20send=20message=20?= =?UTF-8?q?=E2=80=94=20onError=20can=20mean=20queued,=20onMessagePending?= =?UTF-8?q?=20comment,=20callouts=20on=20media=20and=20custom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/send-message.mdx | 100 ++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/sdk/android/v5/send-message.mdx b/sdk/android/v5/send-message.mdx index f5e728696..eb18d0df9 100644 --- a/sdk/android/v5/send-message.mdx +++ b/sdk/android/v5/send-message.mdx @@ -116,7 +116,7 @@ TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -126,6 +126,7 @@ CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.getMessage()); } }); @@ -143,7 +144,7 @@ val textMessage = TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(textMessage: BaseMessage?) { @@ -151,6 +152,7 @@ CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.message) } }) @@ -169,7 +171,7 @@ TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -179,6 +181,7 @@ CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.getMessage()); } }); @@ -195,7 +198,7 @@ private val receiverType = CometChatConstants.RECEIVER_TYPE_GROUP val textMessage = TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(textMessage: BaseMessage?) { @@ -203,6 +206,7 @@ CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.message) } }) @@ -301,6 +305,8 @@ mediaMessage.setTags(tags) + + There are 2 ways you can send Media Messages using the CometChat SDK: 1. **By providing the File :** You can directly share the file object while creating an object of the MediaMessage class. When the media message is sent using the sendMediaMessage() method, this file is then uploaded to CometChat servers and the URL of the file is sent in the success response of the sendMediaMessage() function. @@ -318,7 +324,7 @@ MediaMessage mediaMessage = new MediaMessage(receiverID, new File(filePath), mes CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -328,6 +334,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Media message sending failed with exception: " + e.getMessage()); } }); @@ -345,7 +352,7 @@ private val filePath = "/storage/emulated/0/Download/CometChat.jpg" val mediaMessage = MediaMessage(receiverID, File(filePath), messageType, receiverType) CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -353,6 +360,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Media message sending failed with exception: " + e.message) } }) @@ -372,7 +380,7 @@ MediaMessage mediaMessage = new MediaMessage(receiverID, new File(filePath), mes CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -382,6 +390,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Media message sending failed with exception: " + e.getMessage()); } }); @@ -399,7 +408,7 @@ private val filePath = "/storage/emulated/0/Download/CometChat.jpg" val mediaMessage = MediaMessage(receiverID, File(filePath), messageType, receiverType) CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -407,6 +416,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Media message sending failed with exception: " + e.message) } }) @@ -444,7 +454,7 @@ mediaMessage.setAttachment(attachment); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -454,6 +464,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.e(TAG, e.getMessage()); } }); @@ -476,7 +487,7 @@ mediaMessage.attachment = attachment CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage1: BaseMessage?) { @@ -484,6 +495,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.e(TAG, e.message!!) } }) @@ -507,7 +519,7 @@ mediaMessage.setAttachment(attachment); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -517,6 +529,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.e(TAG, e.getMessage()); } }); @@ -539,7 +552,7 @@ mediaMessage.attachment = attachment CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage1: BaseMessage?) { @@ -547,6 +560,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.e(TAG, e.message!!) } }) @@ -587,7 +601,7 @@ MediaMessage mediaMessage = new MediaMessage(receiverId, files, fileType, CometC CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -597,6 +611,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.getMessage()); } }); @@ -616,7 +631,7 @@ val mediaMessage = MediaMessage(receiverId, files, fileType, CometChatConstants. CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -624,6 +639,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.message) } }) @@ -645,7 +661,7 @@ MediaMessage mediaMessage = new MediaMessage(groupId, files, fileType, CometChat CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -655,6 +671,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.getMessage()); } }); @@ -675,7 +692,7 @@ val mediaMessage = MediaMessage(groupId, files, fileType, CometChatConstants.REC CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -683,6 +700,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.message) } }) @@ -725,7 +743,7 @@ mediaMessage.setAttachments(attachments); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -735,6 +753,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.getMessage()); } }); @@ -761,7 +780,7 @@ mediaMessage.attachments = attachments CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -769,6 +788,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.message) } }) @@ -797,7 +817,7 @@ mediaMessage.setAttachments(attachments); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -807,6 +827,7 @@ CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.getMessage()); } }); @@ -833,7 +854,7 @@ mediaMessage.attachments = attachments CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -841,6 +862,7 @@ CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener( } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message Sending Failed with exception : " + e.message) } }) @@ -919,6 +941,8 @@ customMessage.setTags(tags) Once the object of `CustomMessage` class is ready you can send the custom message using the `sendCustomMessage()` method. + + ```java @@ -934,7 +958,7 @@ CustomMessage customMessage = new CustomMessage(UID, CometChatConstants.RECEIVER CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -944,6 +968,7 @@ CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.getMessage()); } }); @@ -963,7 +988,7 @@ val customMessage = CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, cu CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -971,6 +996,7 @@ CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListene } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.message!!) } }) @@ -992,7 +1018,7 @@ CustomMessage customMessage = new CustomMessage(GUID, CometChatConstants.RECEIVE CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1002,6 +1028,7 @@ CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.getMessage()); } }); @@ -1021,7 +1048,7 @@ val customMessage = CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1029,6 +1056,7 @@ CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListene } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.message!!) } }) @@ -1064,7 +1092,7 @@ customMessage.shouldUpdateConversation(false); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1074,6 +1102,7 @@ CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.getMessage()); } }); @@ -1095,7 +1124,7 @@ customMessage.shouldUpdateConversation(false) CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1103,6 +1132,7 @@ CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListene } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.message!!) } }) @@ -1125,7 +1155,7 @@ customMessage.shouldUpdateConversation(false); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1135,6 +1165,7 @@ CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.getMessage()); } }); @@ -1156,7 +1187,7 @@ customMessage.shouldUpdateConversation(false) CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1164,6 +1195,7 @@ CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListene } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.message!!) } }) @@ -1195,7 +1227,7 @@ customMessage.setConversationText("Custom Notification Body"); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1205,6 +1237,7 @@ CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.getMessage()); } }); @@ -1226,7 +1259,7 @@ customMessage.setConversationText("Custom Notification Body") CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1234,6 +1267,7 @@ CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListene } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.message!!) } }) @@ -1256,7 +1290,7 @@ customMessage.setConversationText("Custom Notification Body"); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1266,6 +1300,7 @@ CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.getMessage()); } }); @@ -1287,7 +1322,7 @@ customMessage.setConversationText("Custom Notification Body") CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1295,6 +1330,7 @@ CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListene } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, e.message!!) } }) From e8725a9c0ec7246ad0bb2d913714a3b295a96bc6 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:04:17 +0530 Subject: [PATCH 21/42] =?UTF-8?q?docs(android-v5):=20upload=20files=20?= =?UTF-8?q?=E2=80=94=20uploads=20never=20resume=20on=20their=20own,=20onEr?= =?UTF-8?q?ror=20can=20mean=20queued?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/upload-files.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/upload-files.mdx b/sdk/android/v5/upload-files.mdx index c451e733e..d0634d3ca 100644 --- a/sdk/android/v5/upload-files.mdx +++ b/sdk/android/v5/upload-files.mdx @@ -17,7 +17,7 @@ The classic path (passing a `File`, or a list of files, straight to the `MediaMe -With offline support enabled (SDK 5.1.0+), an upload batch and its files are persisted on the device: an in-progress batch survives an app restart and can be restored and retried, and a media message you send without a connection is durably queued and sent on reconnect. See [Send While Offline](/sdk/android/v5/offline-send). +With offline support enabled (SDK 5.1.0+), an upload batch and its files are persisted on the device: an in-progress batch survives an app restart, but it never resumes on its own (fetch it with `CometChat.getExistingUploadFileRequest` and retry each failed file), and a media message you send without a connection is durably queued and sent on reconnect. See [Send While Offline](/sdk/android/v5/offline-send). ## The upload-then-send flow @@ -429,7 +429,7 @@ UploadFileListener listener = new UploadFileListener() { CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -440,6 +440,7 @@ UploadFileListener listener = new UploadFileListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Media message sending failed: " + e.getMessage()); } }); @@ -489,7 +490,7 @@ val listener = object : UploadFileListener() { CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(message: BaseMessage?) { @@ -498,6 +499,7 @@ val listener = object : UploadFileListener() { } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Media message sending failed: ${e.message}") } }) From a4d32f201d56ca943a71e2295ead4df1b5114c2e Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:07:45 +0530 Subject: [PATCH 22/42] =?UTF-8?q?docs(android-v5):=20receive=20messages=20?= =?UTF-8?q?=E2=80=94=20onMessagesSynced=20carries=20updates=20only;=20onEr?= =?UTF-8?q?ror=20wording=20on=20every=20offline=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/additional-message-filtering.mdx | 2 +- sdk/android/v5/block-users.mdx | 2 +- sdk/android/v5/campaigns.mdx | 2 +- sdk/android/v5/group-kick-member.mdx | 2 +- sdk/android/v5/mentions.mdx | 2 +- sdk/android/v5/reactions.mdx | 2 +- sdk/android/v5/receive-messages.mdx | 4 ++-- sdk/android/v5/retrieve-group-members.mdx | 2 +- sdk/android/v5/retrieve-groups.mdx | 2 +- sdk/android/v5/retrieve-users.mdx | 2 +- sdk/android/v5/threaded-messages.mdx | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sdk/android/v5/additional-message-filtering.mdx b/sdk/android/v5/additional-message-filtering.mdx index f61a487d4..e70fe4deb 100644 --- a/sdk/android/v5/additional-message-filtering.mdx +++ b/sdk/android/v5/additional-message-filtering.mdx @@ -1376,7 +1376,7 @@ For the full save workflow — saving, unsaving, limits and real-time events — ## Offline Behaviour -With offline support enabled, `fetchPrevious`/`fetchNext` answer from the on-device cache first (`onCachedMessages`, even when empty) and then from the server (`onServerMessages`). The cache answers a page **only when every filter on the request is locally computable** — a single server-only filter makes the cached page empty with no error, while the server phase still answers in full. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `fetchPrevious`/`fetchNext` answer from the on-device cache first (`onCachedMessages`, even when empty) and then from the server (`onServerMessages`). The cache answers a page **only when every filter on the request is locally computable** — a single server-only filter makes the cached page empty with no error, while the server phase still answers in full. Offline, the fetch ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline behaviour | | --- | --- | diff --git a/sdk/android/v5/block-users.mdx b/sdk/android/v5/block-users.mdx index 41d307bf9..2debb02f3 100644 --- a/sdk/android/v5/block-users.mdx +++ b/sdk/android/v5/block-users.mdx @@ -266,7 +266,7 @@ override fun onError(e: CometChatException) { ## Offline Behaviour -With offline support enabled, `BlockedUsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the device (empty when nothing was recorded), then `onServerUsers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `BlockedUsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the device (empty when nothing was recorded), then `onServerUsers` with the live page to reconcile by `uid`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/campaigns.mdx b/sdk/android/v5/campaigns.mdx index 64c5182ca..8eef05153 100644 --- a/sdk/android/v5/campaigns.mdx +++ b/sdk/android/v5/campaigns.mdx @@ -342,7 +342,7 @@ Call `fetchNext()` repeatedly for pagination. When the server has no more items, ## Offline Behaviour -With offline support enabled, `NotificationFeedRequest.fetchNext()` on a `NotificationFeedFetchListener` first delivers `onCachedNotificationFeedItems` from the device (empty when nothing was recorded), then `onServerNotificationFeedItems` with the live page to reconcile by item `id`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `NotificationFeedRequest.fetchNext()` on a `NotificationFeedFetchListener` first delivers `onCachedNotificationFeedItems` from the device (empty when nothing was recorded), then `onServerNotificationFeedItems` with the live page to reconcile by item `id`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/group-kick-member.mdx b/sdk/android/v5/group-kick-member.mdx index 56beb8e2d..b480b28e5 100644 --- a/sdk/android/v5/group-kick-member.mdx +++ b/sdk/android/v5/group-kick-member.mdx @@ -296,7 +296,7 @@ bannedGroupMembersRequest?.fetchNext(object :CometChat.BannedGroupMemberFetchLis ## Offline Behaviour -With offline support enabled, `BannedGroupMembersRequest.fetchNext()` on a `BannedGroupMemberFetchListener` first delivers `onCachedBannedGroupMembers` from the device (empty when nothing was recorded), then `onServerBannedGroupMembers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `BannedGroupMembersRequest.fetchNext()` on a `BannedGroupMemberFetchListener` first delivers `onCachedBannedGroupMembers` from the device (empty when nothing was recorded), then `onServerBannedGroupMembers` with the live page to reconcile by `uid`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/mentions.mdx b/sdk/android/v5/mentions.mdx index d64dfe892..11155a64e 100644 --- a/sdk/android/v5/mentions.mdx +++ b/sdk/android/v5/mentions.mdx @@ -466,7 +466,7 @@ message.hasMentionedMe ## Offline Behaviour -With offline support enabled, these mention fetches run in two phases: `onCachedMessages` delivers the cached page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, these mention fetches run in two phases: `onCachedMessages` delivers the cached page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). `mentionsWithTagInfo` and `mentionsWithBlockedInfo` enrich a read without narrowing it, so they never empty the cached page: the cached phase serves the matching messages as stored, and the server phase supplies the mentioned users' tag and blocked-relationship detail. diff --git a/sdk/android/v5/reactions.mdx b/sdk/android/v5/reactions.mdx index 110216631..f670ddf86 100644 --- a/sdk/android/v5/reactions.mdx +++ b/sdk/android/v5/reactions.mdx @@ -254,7 +254,7 @@ reactionsRequest.fetchPrevious(object : CometChat.ReactionFetchListener() { ## Offline Behaviour -With offline support enabled, `ReactionsRequest.fetchNext()` and `fetchPrevious()` on a `ReactionFetchListener` first deliver `onCachedReactions` from the device (empty when nothing was recorded), then `onServerReactions` with the live page to reconcile by reaction id. Each direction is cached independently, and offline a read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `ReactionsRequest.fetchNext()` and `fetchPrevious()` on a `ReactionFetchListener` first deliver `onCachedReactions` from the device (empty when nothing was recorded), then `onServerReactions` with the live page to reconcile by reaction id. Each direction is cached independently, and offline a read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/receive-messages.mdx b/sdk/android/v5/receive-messages.mdx index 1f25470a4..11307dcfc 100644 --- a/sdk/android/v5/receive-messages.mdx +++ b/sdk/android/v5/receive-messages.mdx @@ -112,7 +112,7 @@ As a sender, you will not receive your own message in a real-time message event. -With offline support enabled, a registered `MessageListener` also receives `onMessagesSynced(List messages)` after the SDK catches up on reconnect, carrying the messages that arrived while the device was offline. See [Stay in Sync](/sdk/android/v5/offline-sync). +With offline support enabled, a registered `MessageListener` also receives `onMessagesSynced(List messages)` after the SDK catches up on reconnect, carrying updates to already-cached messages that changed while offline: reactions, edits, deletes and receipts. Messages that arrived while offline still come one at a time through the usual `onTextMessageReceived`, `onMediaMessageReceived` and other typed callbacks. See [Stay in Sync](/sdk/android/v5/offline-sync). @@ -1298,7 +1298,7 @@ In the `onSuccess()` callback, you will receive a hashmap which will contain the ## Offline Behaviour -With offline support enabled, message fetches run in two phases: `onCachedMessages` delivers a page from the on-device cache first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, message fetches run in two phases: `onCachedMessages` delivers a page from the on-device cache first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline behaviour | | --- | --- | diff --git a/sdk/android/v5/retrieve-group-members.mdx b/sdk/android/v5/retrieve-group-members.mdx index ef563a415..4dab44a58 100644 --- a/sdk/android/v5/retrieve-group-members.mdx +++ b/sdk/android/v5/retrieve-group-members.mdx @@ -162,7 +162,7 @@ groupMembersRequest?.fetchNext(object:CometChat.GroupMemberFetchListener(){ ## Offline Behaviour -With offline support enabled, `GroupMembersRequest.fetchNext()` on a `GroupMemberFetchListener` first delivers `onCachedGroupMembers` from the device (empty when nothing was recorded), then `onServerGroupMembers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `GroupMembersRequest.fetchNext()` on a `GroupMemberFetchListener` first delivers `onCachedGroupMembers` from the device (empty when nothing was recorded), then `onServerGroupMembers` with the live page to reconcile by `uid`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/retrieve-groups.mdx b/sdk/android/v5/retrieve-groups.mdx index a45c01833..547a9c35f 100644 --- a/sdk/android/v5/retrieve-groups.mdx +++ b/sdk/android/v5/retrieve-groups.mdx @@ -314,7 +314,7 @@ This method returns a `Hashmap` with the GUID of the group as the key and the on ## Offline Behaviour -With offline support enabled, `GroupsRequest.fetchNext()` on a `GroupFetchListener` first delivers `onCachedGroups` from the device (empty when nothing was recorded), then `onServerGroups` with the live page to reconcile by `guid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `GroupsRequest.fetchNext()` on a `GroupFetchListener` first delivers `onCachedGroups` from the device (empty when nothing was recorded), then `onServerGroups` with the live page to reconcile by `guid`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/retrieve-users.mdx b/sdk/android/v5/retrieve-users.mdx index 83f5e8b91..54d2945fe 100644 --- a/sdk/android/v5/retrieve-users.mdx +++ b/sdk/android/v5/retrieve-users.mdx @@ -548,7 +548,7 @@ override fun onError(e: CometChatException) { ## Offline Behaviour -With offline support enabled, `UsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the device — the pages the server last returned for this exact request, replayed on-device (empty when nothing was recorded) — then `onServerUsers` with the live page to reconcile by `uid`. Offline, the read ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `UsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the device — the pages the server last returned for this exact request, replayed on-device (empty when nothing was recorded) — then `onServerUsers` with the live page to reconcile by `uid`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/threaded-messages.mdx b/sdk/android/v5/threaded-messages.mdx index 5f04dd494..547444f41 100644 --- a/sdk/android/v5/threaded-messages.mdx +++ b/sdk/android/v5/threaded-messages.mdx @@ -279,7 +279,7 @@ The above snippet will return messages between the logged in user and `cometchat ## Offline Behaviour -With offline support enabled, thread fetches run in two phases: `onCachedMessages` delivers the cached thread page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError(CometChatConstants.Errors.ERROR_INTERNET_UNAVAILABLE)` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, thread fetches run in two phases: `onCachedMessages` delivers the cached thread page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline behaviour | | --- | --- | From dbbb3480354aaa00871bd4e9e7990de06d68b66d Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:09:45 +0530 Subject: [PATCH 23/42] =?UTF-8?q?docs(android-v5):=20real-time=20listeners?= =?UTF-8?q?=20=E2=80=94=20onMessagesSynced=20carries=20updates=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/real-time-listeners.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/android/v5/real-time-listeners.mdx b/sdk/android/v5/real-time-listeners.mdx index 80efec755..adf91b591 100644 --- a/sdk/android/v5/real-time-listeners.mdx +++ b/sdk/android/v5/real-time-listeners.mdx @@ -166,7 +166,7 @@ CometChat.removeGroupListener(UNIQUE_LISTENER_ID) The `MessageListener` class provides you with live events related to messages. Below are the callback methods provided by the `MessageListener` class. -With offline support enabled, `MessageListener` also receives `onMessagesSynced`, delivered after the SDK catches up on messages it missed while offline. See [Stay in Sync](/sdk/android/v5/offline-sync). +With offline support enabled, `MessageListener` also receives `onMessagesSynced` after the SDK catches up on reconnect. It carries updates to already-cached messages that changed while offline (reactions, edits, deletes and receipts); messages that arrived while offline still come through the typed callbacks such as `onTextMessageReceived`. See [Stay in Sync](/sdk/android/v5/offline-sync). | Method | Information | From 116af0de7c05d0979ce68f8cf9624542aa288cd2 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:14:04 +0530 Subject: [PATCH 24/42] =?UTF-8?q?docs(android-v5):=20real-time=20listeners?= =?UTF-8?q?=20=E2=80=94=20list=20all=20six=20listeners,=20add=20an=20offli?= =?UTF-8?q?ne=20listeners=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/real-time-listeners.mdx | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/real-time-listeners.mdx b/sdk/android/v5/real-time-listeners.mdx index adf91b591..287f8a9ab 100644 --- a/sdk/android/v5/real-time-listeners.mdx +++ b/sdk/android/v5/real-time-listeners.mdx @@ -1,17 +1,20 @@ --- title: "All Real Time Listeners" -description: "Covers the five SDK listeners for users, groups, messages, calls, and AI assistant, so readers can wire up live event callbacks for each one." +description: "Covers the SDK listeners for users, groups, messages, conversations, AI assistant and calls, plus the offline listeners, so readers can wire up callbacks for each one." --- -CometChat provides 4 listeners viz. +CometChat provides these real-time listeners: 1. [UserListener](#user-listener) 2. [GroupListener](#group-listener) 3. [MessageListener](#message-listener) -4. [CallListener](#call-listener) +4. [ConversationListener](#conversation-listener) 5. [AIAssistantListener](#ai-assistant-listener) +6. [CallListener](#call-listener) + +With offline support enabled, four more listeners report what the SDK does on the device; see [Offline listeners](#offline-listeners). ## User Listener @@ -562,3 +565,14 @@ CometChat.addCallListener(UNIQUE_LISTENER_ID, object : CometChat.CallListener() Where `UNIQUE_LISTENER_ID` is the unique identifier for the listener. Make sure that no two listeners are added with the same listener ID as this could lead to unexpected behavior resulting in loss of events. Once the activity/fragment where the `MessageListener` is declared is not in use, you need to remove the listener using the `removeMessageListener()` method which takes the id of the listener to be removed as the parameter. We suggest you call this method in the `onPause()` method of the activity/fragment. + +## Offline listeners + +With offline support enabled (SDK 5.1.0+), these listeners report what the SDK does on the device rather than events from the server. Each is documented with examples on its own page. + +| Listener | What it reports | Details | +|---|---|---| +| `OfflineMessageListener` | Every outbox transition, app-wide: a message queued, sent or failed | [Send While Offline](/sdk/android/v5/offline-send) | +| `SyncListener` | Progress and outcome of each catch-up sync | [Stay in Sync](/sdk/android/v5/offline-sync) | +| `OfflineCacheResetListener` | The on-device cache was rebuilt, for example after the encryption key was lost | [Encrypt the Cache](/sdk/android/v5/offline-encryption) | +| `MediaDownloadListener` | A background media download finished or failed | [Media Caching](/sdk/android/v5/offline-media-caching) | From 94b9d71c4f3ce22bf7cd5bb0915734f5e8714fb4 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:15:05 +0530 Subject: [PATCH 25/42] =?UTF-8?q?docs(android-v5):=20additional=20message?= =?UTF-8?q?=20filtering=20=E2=80=94=20server-only=20filters,=20setUpdatedA?= =?UTF-8?q?fter,=20setParentMessageId(long),=20pinned/saved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/additional-message-filtering.mdx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/additional-message-filtering.mdx b/sdk/android/v5/additional-message-filtering.mdx index e70fe4deb..6dccfc3ce 100644 --- a/sdk/android/v5/additional-message-filtering.mdx +++ b/sdk/android/v5/additional-message-filtering.mdx @@ -1382,17 +1382,18 @@ With offline support enabled, `fetchPrevious`/`fetchNext` answer from the on-dev | --- | --- | | `setUnread(boolean)` | Cached | | `hideMessagesFromBlockedUsers(boolean)` | Cached | -| `setUpdatedAfter(long)` | Cached | | `updatesOnly(boolean)` | Server only — cached page is empty | | `setCategories(List)` | Cached | | `setTypes(List)` | Cached | -| `setParentMessageId(int)` | Cached | +| `setParentMessageId(long)` | Cached | | `hideReplies(boolean)` | Cached | | `hideDeletedMessages(boolean)` | Cached | | `hideQuotedMessages(boolean)` | Cached | | `setTags(List)` | Cached | | `withTags(boolean)` | Cached | | `hasLinks(boolean)` | Server only — cached page is empty | +| `setSearchKeyword(String)` | Server only — cached page is empty | +| `setInteractionGoalCompletedOnly(boolean)` | Server only — cached page is empty | | `hasAttachments(boolean)` | Cached | | `hasReactions(boolean)` | Cached | | `setAttachmentTypes(List)` | Cached | @@ -1401,4 +1402,6 @@ With offline support enabled, `fetchPrevious`/`fetchNext` answer from the on-dev | `setPinned(boolean)` | Cached | | `setSaved(boolean)` | Cached | -`setMessageId`, `setTimestamp`, `setLimit`, `setUID` and `setGUID` are scope and pagination inputs, not filters, and do not change what the cache can answer. Setting both `setUID` and `setGUID` together on a non-thread read (one sender's messages in a group) is answered by the server only. +`setPinned` and `setSaved` are served from separate pinned and saved stores, not the message cache, so the other filters in this table do not apply to a pinned or saved read. + +`setMessageId`, `setTimestamp`, `setUpdatedAfter`, `setLimit`, `setUID` and `setGUID` are scope and pagination inputs, not filters, and do not change what the cache can answer. Setting both `setUID` and `setGUID` together on a non-thread read (one sender's messages in a group) is answered by the server only. From 80772aa3cc0181d24deff4e5916b2bb8d5917684 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:15:55 +0530 Subject: [PATCH 26/42] =?UTF-8?q?docs(android-v5):=20threaded=20messages?= =?UTF-8?q?=20=E2=80=94=20thread=20reads=20cached=20with=20UID=20and=20GUI?= =?UTF-8?q?D,=20setParentMessageId(long)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/threaded-messages.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/android/v5/threaded-messages.mdx b/sdk/android/v5/threaded-messages.mdx index 547444f41..e706d30ec 100644 --- a/sdk/android/v5/threaded-messages.mdx +++ b/sdk/android/v5/threaded-messages.mdx @@ -279,11 +279,11 @@ The above snippet will return messages between the logged in user and `cometchat ## Offline Behaviour -With offline support enabled, thread fetches run in two phases: `onCachedMessages` delivers the cached thread page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, thread fetches run in two phases: `onCachedMessages` delivers the cached thread page first (even when empty), then `onServerMessages` delivers the reconciled server copy. Offline, the fetch ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. A thread read is answered from the cache even when both `setUID` and `setGUID` are set; unlike a non-thread read, that pairing is not server-only here. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline behaviour | | --- | --- | -| `setParentMessageId(int)` | Cached | +| `setParentMessageId(long)` | Cached | | `hideReplies(boolean)` | Cached | ## Subscribe to a Thread From 8573d8cc65313d0e9bad4bec44e3e6ecd9bb335c Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:17:20 +0530 Subject: [PATCH 27/42] =?UTF-8?q?docs(android-v5):=20thread=20subscription?= =?UTF-8?q?=20=E2=80=94=20participatedByMe=20cache=20sources;=20onError=20?= =?UTF-8?q?wording=20on=20five=20more=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/pin-conversation.mdx | 2 +- sdk/android/v5/pin-message.mdx | 2 +- sdk/android/v5/retrieve-conversations.mdx | 2 +- sdk/android/v5/save-message.mdx | 2 +- sdk/android/v5/thread-subscription.mdx | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/android/v5/pin-conversation.mdx b/sdk/android/v5/pin-conversation.mdx index d2659a38f..b8869ce9d 100644 --- a/sdk/android/v5/pin-conversation.mdx +++ b/sdk/android/v5/pin-conversation.mdx @@ -398,7 +398,7 @@ if (CometChat.isPinConversationEnabled()) { ## Offline Behaviour -With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setPinnedBy()` request delivers a cached page first (`onCachedConversations`, even when empty), then the server list (`onServerConversations`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedConversations` holds the pinned conversations the cache already knows about, in the same order. +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setPinnedBy()` request delivers a cached page first (`onCachedConversations`, even when empty), then the server list (`onServerConversations`); offline, it ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. `onCachedConversations` holds the pinned conversations the cache already knows about, in the same order. | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/pin-message.mdx b/sdk/android/v5/pin-message.mdx index 7b7eb3ca3..1f105f6d0 100644 --- a/sdk/android/v5/pin-message.mdx +++ b/sdk/android/v5/pin-message.mdx @@ -454,7 +454,7 @@ if (CometChat.isPinMessageEnabled()) { ## Offline Behaviour -With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setPinned(true)` request delivers a cached page first (`onCachedMessages`, even when empty) and then the server list (`onServerMessages`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedMessages` holds the conversation's cached pinned messages, newest pin first. The pinned list is answered from its own store, so other `MessagesRequestBuilder` filters are ignored, as on the server. +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setPinned(true)` request delivers a cached page first (`onCachedMessages`, even when empty) and then the server list (`onServerMessages`); offline, it ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. `onCachedMessages` holds the conversation's cached pinned messages, newest pin first. The pinned list is answered from its own store, so other `MessagesRequestBuilder` filters are ignored, as on the server. | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/retrieve-conversations.mdx b/sdk/android/v5/retrieve-conversations.mdx index 4a8b52f6a..956a24c1c 100644 --- a/sdk/android/v5/retrieve-conversations.mdx +++ b/sdk/android/v5/retrieve-conversations.mdx @@ -570,7 +570,7 @@ While converting a `Message` object to a `Conversation` object, the `unreadMessa ## Offline Behaviour -With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` delivers a cached page first (`onCachedConversations`, even when empty) and then the server list (`onServerConversations`); offline, the read ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. The cached page is answered only when every builder filter on the request is locally computable — a single server-only filter blanks the cached page while the server phase still answers in full. +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` delivers a cached page first (`onCachedConversations`, even when empty) and then the server list (`onServerConversations`); offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. The cached page is answered only when every builder filter on the request is locally computable — a single server-only filter blanks the cached page while the server phase still answers in full. | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/save-message.mdx b/sdk/android/v5/save-message.mdx index 52235453d..5372c32da 100644 --- a/sdk/android/v5/save-message.mdx +++ b/sdk/android/v5/save-message.mdx @@ -383,7 +383,7 @@ if (CometChat.isSaveMessageEnabled()) { ## Offline Behaviour -With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setSaved(true)` request delivers a cached page first (`onCachedMessages`, even when empty) and then the server list (`onServerMessages`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedMessages` holds the user's cached saved messages across all conversations. The saved list is answered from its own store, so other `MessagesRequestBuilder` filters are ignored, as on the server. +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` on a `setSaved(true)` request delivers a cached page first (`onCachedMessages`, even when empty) and then the server list (`onServerMessages`); offline, it ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. `onCachedMessages` holds the user's cached saved messages across all conversations. The saved list is answered from its own store, so other `MessagesRequestBuilder` filters are ignored, as on the server. | Builder method | Offline | |---|---| diff --git a/sdk/android/v5/thread-subscription.mdx b/sdk/android/v5/thread-subscription.mdx index a6033334a..30d004474 100644 --- a/sdk/android/v5/thread-subscription.mdx +++ b/sdk/android/v5/thread-subscription.mdx @@ -423,11 +423,11 @@ See [Notification Preferences](/notifications) for how to read and update a user ## Offline Behaviour -With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` delivers a cached page first (`onCachedThreads`, even when empty) and then the server list (`onServerThreads`); offline, it ends with `onError(ERROR_INTERNET_UNAVAILABLE)` after the cached page. `onCachedThreads` holds the user's cached participated threads, in the same sort order; `setUid()` / `setGuid()` scope that list to one conversation. +With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext()` delivers a cached page first (`onCachedThreads`, even when empty) and then the server list (`onServerThreads`); offline, it ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. With `setParticipatedByMe(true)`, `onCachedThreads` holds the user's cached participated threads, in the same sort order; with `false`, it replays the last page fetched online for this request. `setUid()` / `setGuid()` scope that list to one conversation. | Builder method | Offline | |---|---| -| `setParticipatedByMe` | Cached | +| `setParticipatedByMe` | Cached (`true`: your participated threads; `false`: the last page fetched online) | | `setUid` / `setGuid` | Cached (scope) | ## Next Steps From eb9f07d26b69e5dc2c61c79b8772266b7b3e9bee Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:18:37 +0530 Subject: [PATCH 28/42] =?UTF-8?q?docs(android-v5):=20mentions=20=E2=80=94?= =?UTF-8?q?=20onError=20can=20mean=20queued,=20pending=20log=20wording?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/mentions.mdx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sdk/android/v5/mentions.mdx b/sdk/android/v5/mentions.mdx index 11155a64e..aef3b0d45 100644 --- a/sdk/android/v5/mentions.mdx +++ b/sdk/android/v5/mentions.mdx @@ -32,7 +32,7 @@ TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - Log.d(TAG, "Message queued"); + Log.d(TAG, "Message pending"); } @Override @@ -42,6 +42,7 @@ CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.getMessage()); } }); @@ -58,7 +59,7 @@ private val receiverType = CometChatConstants.RECEIVER_TYPE_USER val textMessage = TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - Log.d(TAG, "Message queued") + Log.d(TAG, "Message pending") } override fun onMessageSent(message: BaseMessage?) { @@ -66,6 +67,7 @@ CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.message) } }) @@ -84,7 +86,7 @@ TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - Log.d(TAG, "Message queued"); + Log.d(TAG, "Message pending"); } @Override @@ -94,6 +96,7 @@ CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.getMessage()); } }); @@ -110,7 +113,7 @@ private val receiverType = CometChatConstants.RECEIVER_TYPE_GROUP val textMessage = TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - Log.d(TAG, "Message queued") + Log.d(TAG, "Message pending") } override fun onMessageSent(message: BaseMessage?) { @@ -118,6 +121,7 @@ CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.d(TAG, "Message sending failed with exception: " + e.message) } }) From 606122c5bb29773ed9b6042fe7bbe07a5334be79 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:19:46 +0530 Subject: [PATCH 29/42] =?UTF-8?q?docs(android-v5):=20reactions=20=E2=80=94?= =?UTF-8?q?=20one=20direction=20per=20request,=20replayed=20pages,=20limit?= =?UTF-8?q?=20default=20and=20max?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/reactions.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/android/v5/reactions.mdx b/sdk/android/v5/reactions.mdx index f670ddf86..773bf71f1 100644 --- a/sdk/android/v5/reactions.mdx +++ b/sdk/android/v5/reactions.mdx @@ -107,7 +107,7 @@ CometChat.removeReaction(messageId, emoji, object : CometChat.CallbackListener Date: Sun, 1 Nov 2026 18:21:23 +0530 Subject: [PATCH 30/42] =?UTF-8?q?docs(android-v5):=20edit=20and=20delete?= =?UTF-8?q?=20message=20=E2=80=94=20cache=20update=20after=20onSuccess,=20?= =?UTF-8?q?cached=20messages=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/delete-message.mdx | 2 +- sdk/android/v5/edit-message.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/android/v5/delete-message.mdx b/sdk/android/v5/delete-message.mdx index d5caf2c25..c1fc45b69 100644 --- a/sdk/android/v5/delete-message.mdx +++ b/sdk/android/v5/delete-message.mdx @@ -17,7 +17,7 @@ While [deleting a message](/sdk/android/v5/delete-message#delete-a-message) is s In case you have to delete a message, you can use the `deleteMessage()` method. This method takes the message ID of the message to be deleted. -Deleting a message needs a connection — with offline support enabled, `deleteMessage()` is **not** queued for later. On success the deletion is written to the on-device cache. See [Send While Offline](/sdk/android/v5/offline-send). +Deleting a message needs a connection — with offline support enabled, `deleteMessage()` is **not** queued for later. After `onSuccess`, the deletion is applied to the message in the on-device cache if it is already cached; it never adds a new message. See [Send While Offline](/sdk/android/v5/offline-send). diff --git a/sdk/android/v5/edit-message.mdx b/sdk/android/v5/edit-message.mdx index f2c0f8023..576ee88ee 100644 --- a/sdk/android/v5/edit-message.mdx +++ b/sdk/android/v5/edit-message.mdx @@ -17,7 +17,7 @@ While [editing a message](/sdk/android/v5/edit-message#edit-a-message) is straig In order to edit a message, you can use the `editMessage()` method. This method takes an object of the `BaseMessage` class. At the moment, you are only allowed to edit `TextMessage` and `CustomMessage`. Thus, the `BaseMessage` object must either be a Text or a Custom Message. -Editing a message needs a connection — with offline support enabled, `editMessage()` is **not** queued for later. On success the edited message is updated in the on-device cache. See [Send While Offline](/sdk/android/v5/offline-send). +Editing a message needs a connection — with offline support enabled, `editMessage()` is **not** queued for later. After `onSuccess`, the edit is applied to the message in the on-device cache if it is already cached; it never adds a new message. See [Send While Offline](/sdk/android/v5/offline-send). ### Add/Update Tags From 3df930960b201a712c9b3b5a602af3645d862025 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:23:56 +0530 Subject: [PATCH 31/42] =?UTF-8?q?docs(android-v5):=20AI=20moderation=20?= =?UTF-8?q?=E2=80=94=20onMessagePending=20in=20the=20flow,=20onError=20can?= =?UTF-8?q?=20mean=20queued?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/ai-moderation.mdx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sdk/android/v5/ai-moderation.mdx b/sdk/android/v5/ai-moderation.mdx index 26b2a89a3..1436ea28e 100644 --- a/sdk/android/v5/ai-moderation.mdx +++ b/sdk/android/v5/ai-moderation.mdx @@ -31,6 +31,7 @@ sequenceDiagram participant Moderation App->>SDK: sendMessage() + SDK-->>App: onMessagePending (offline support on) SDK->>CometChat: Send Message CometChat->>Moderation: Process Message CometChat-->>SDK: Message (status: PENDING) @@ -43,7 +44,7 @@ sequenceDiagram | Step | Description | |------|-------------| | 1. Send Message | App sends a text, image, or video message | -| 2. Pending Status | Message is sent with `PENDING` moderation status | +| 2. Pending Status | Message is sent with `PENDING` moderation status (`getModerationStatus()`). This is separate from the send status: with offline support on, `onMessagePending` fires first, before the message reaches the server. | | 3. AI Processing | Moderation service analyzes the content | | 4. Result Event | `onMessageModerated` event fires with final status | @@ -84,7 +85,7 @@ When you send a text, image, or video message, check the initial moderation stat CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Durably queued when offline; message?.status == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(message: BaseMessage?) { @@ -96,6 +97,7 @@ When you send a text, image, or video message, check the initial moderation stat } override fun onError(e: CometChatException) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.e(TAG, "Message sending failed: ${e.message}") } }) @@ -108,7 +110,7 @@ When you send a text, image, or video message, check the initial moderation stat CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Durably queued when offline; message.getStatus() == MessageStatus.PENDING + // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -123,6 +125,7 @@ When you send a text, image, or video message, check the initial moderation stat @Override public void onError(CometChatException e) { + // With offline support on, this also fires when a message is queued offline: check the message's status (PENDING = queued, FAILED = failed). Log.e(TAG, "Message sending failed: " + e.getMessage()); } }); From 129545fa7529c680fa2a72a00fff11f978d4d344 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:26:31 +0530 Subject: [PATCH 32/42] docs(android-v5): retrieve-conversations offline table and getConversation offline note Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/retrieve-conversations.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/android/v5/retrieve-conversations.mdx b/sdk/android/v5/retrieve-conversations.mdx index 956a24c1c..f7e99e1f1 100644 --- a/sdk/android/v5/retrieve-conversations.mdx +++ b/sdk/android/v5/retrieve-conversations.mdx @@ -494,7 +494,7 @@ In order to fetch a specific conversation, you can use the `getConversation` met * group - Only fetches group conversations. -**Starting with v5.1.0, the `CallbackListener` overload of `getConversation()` is deprecated.** Use `ConversationDetailsListener` instead; it requires SDK 5.1.0 or later. The server copy arrives in `onServerConversation` instead of `onSuccess`, and `onError` is unchanged. With offline support on, `onCachedConversation` first delivers the cached copy when there is one. On 5.0.x, keep using `CallbackListener`. +**Starting with v5.1.0, the `CallbackListener` overload of `getConversation()` is deprecated.** Use `ConversationDetailsListener` instead; it requires SDK 5.1.0 or later. The server copy arrives in `onServerConversation` instead of `onSuccess`, and `onError` is unchanged. With offline support on, `onCachedConversation` first delivers the cached copy when there is one. Offline, the read then ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE`; when nothing is cached, `onError` is the only callback. On 5.0.x, keep using `CallbackListener`. @@ -580,4 +580,6 @@ With [offline support](/sdk/android/v5/offline-cached-data) enabled, `fetchNext( | `setUnread` | Cached (last-known count; the server phase corrects it) | | `includeBlockedUsers` / `withBlockedInfo` | Cached | | `withUserAndGroupTags` / `withTags` | Cached (enrichment) | +| `hideAgentic` / `onlyAgentic` | Cached | +| `setPinnedBy` | Cached | | `setSearchKeyword` | Server only — cached page is empty | From 4356ed21c71de2cb5c17185a95cd17a76e28562d Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:56:16 +0530 Subject: [PATCH 33/42] docs(android-v5): retrieve-users offline table adds searchIn Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/retrieve-users.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/android/v5/retrieve-users.mdx b/sdk/android/v5/retrieve-users.mdx index 54d2945fe..e55d95063 100644 --- a/sdk/android/v5/retrieve-users.mdx +++ b/sdk/android/v5/retrieve-users.mdx @@ -554,6 +554,7 @@ With offline support enabled, `UsersRequest.fetchNext()` on a `UserFetchListener |---|---| | `setLimit` | Cached | | `setSearchKeyword` | Server only — cached page is empty | +| `searchIn` | Used only with `setSearchKeyword`, so server only | | `setUserStatus` | Server only — cached page is empty | | `hideBlockedUsers` | Cached | | `setRoles` / `setRole` | Cached | From 328ab7f81173c790b1312b19c6035c275827ed72 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:57:34 +0530 Subject: [PATCH 34/42] docs(android-v5): block-users cached page comes from the blocked-users list Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/block-users.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/android/v5/block-users.mdx b/sdk/android/v5/block-users.mdx index 2debb02f3..ddc466dca 100644 --- a/sdk/android/v5/block-users.mdx +++ b/sdk/android/v5/block-users.mdx @@ -266,7 +266,7 @@ override fun onError(e: CometChatException) { ## Offline Behaviour -With offline support enabled, `BlockedUsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the device (empty when nothing was recorded), then `onServerUsers` with the live page to reconcile by `uid`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. A request that uses a server-only filter records nothing, so its cached page is always empty. See [Show Cached Data](/sdk/android/v5/offline-cached-data). +With offline support enabled, `BlockedUsersRequest.fetchNext()` on a `UserFetchListener` first delivers `onCachedUsers` from the SDK's own blocked-users list, which it refreshes on login and on reconnect and updates when you call `blockUsers()` or `unblockUsers()`. Then `onServerUsers` delivers the live page to reconcile by `uid`; the server page is not saved to the device. The cached page is empty when no users are blocked or when `setSearchKeyword` is set. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). | Builder method | Offline | |---|---| From 2526c95429521ab9ef9df92af37a186b74b38802 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:59:09 +0530 Subject: [PATCH 35/42] docs(android-v5): retrieve-group-members documents setStatus Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/retrieve-group-members.mdx | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/sdk/android/v5/retrieve-group-members.mdx b/sdk/android/v5/retrieve-group-members.mdx index 4dab44a58..4daf81b4e 100644 --- a/sdk/android/v5/retrieve-group-members.mdx +++ b/sdk/android/v5/retrieve-group-members.mdx @@ -98,6 +98,33 @@ val groupMembersRequest = GroupMembersRequestBuilder(GUID) +### Set Status + +With offline support on, this filter is answered by the server only; the cached page is empty. + +This method allows you to fetch only the group members who are online or offline. Pass `UsersRequest.USER_STATUS_ONLINE` or `UsersRequest.USER_STATUS_OFFLINE`; leave it unset to fetch all members. + + + +```java +GroupMembersRequest groupMembersRequest = new GroupMembersRequest.GroupMembersRequestBuilder(GUID) + .setStatus(UsersRequest.USER_STATUS_ONLINE) + .build(); +``` + + + + +```kotlin +val groupMembersRequest = GroupMembersRequestBuilder(GUID) + .setStatus(UsersRequest.USER_STATUS_ONLINE) + .build() +``` + + + + + Finally, once all the parameters are set to the builder class, you need to call the `build()` method to get the object of the `GroupMembersRequest` class. Once you have the object of the `GroupMembersRequest` class, you need to call the `fetchNext()` method. Calling this method will return a list of `GroupMember` objects containing N number of members depending on the limit set. From 22a9edf732e439e324e3a9d613ffee61df73df5a Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 18:59:57 +0530 Subject: [PATCH 36/42] docs(android-v5): group-kick-member documents banned-members setScopes Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/group-kick-member.mdx | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sdk/android/v5/group-kick-member.mdx b/sdk/android/v5/group-kick-member.mdx index b480b28e5..3a73bb180 100644 --- a/sdk/android/v5/group-kick-member.mdx +++ b/sdk/android/v5/group-kick-member.mdx @@ -232,6 +232,37 @@ val bannedGroupMembersRequest = BannedGroupMembersRequestBuilder(GUID) +### Set Scopes + +This method allows you to fetch banned group members based on the specified scopes. + + + +```java +List scopes = new ArrayList<>(); +scopes.add("admin"); +scopes.add("moderator"); +BannedGroupMembersRequest bannedGroupMembersRequest = new BannedGroupMembersRequest.BannedGroupMembersRequestBuilder(GUID) + .setScopes(scopes) + .build(); +``` + + + + +```kotlin +val scopes: MutableList = ArrayList() +scopes.add("admin") +scopes.add("moderator") +val bannedGroupMembersRequest = BannedGroupMembersRequestBuilder(GUID) + .setScopes(scopes) + .build() +``` + + + + + Finally, once all the parameters are set to the builder class, you need to call the `build()` method to get the object of the `BannedGroupMembersRequest` class. Once you have the object of the `BannedGroupMembersRequest` class, you need to call the `fetchNext()` method. Calling this method will return a list of `GroupMember` objects containing n number of banned members where n is the limit set in the builder class. @@ -301,6 +332,7 @@ With offline support enabled, `BannedGroupMembersRequest.fetchNext()` on a `Bann | Builder method | Offline | |---|---| | `setLimit` | Cached | +| `setScopes` | Cached | | `setSearchKeyword` | Server only — cached page is empty | ## Real-Time Group Member Kicked/Banned Events From 7d9a6cf65c9a9ebcec7cce271b2b2d6025e82dc4 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 19:11:15 +0530 Subject: [PATCH 37/42] docs(android-v5): delete-group needs a connection; cache clears on the event or next sync Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/delete-group.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/android/v5/delete-group.mdx b/sdk/android/v5/delete-group.mdx index b1bdea83a..ce071dad4 100644 --- a/sdk/android/v5/delete-group.mdx +++ b/sdk/android/v5/delete-group.mdx @@ -10,7 +10,7 @@ description: "Only a group admin can remove it, and this page shows the single p To delete a group you need to use the `deleteGroup()` method. The user must be an **Admin** of the group they are trying to delete. -With offline support enabled, deleting a group removes that group's conversation from the on-device cache; any messages still queued to it are dropped and reported as failed with reason `DISCARDED`. See [Send While Offline](/sdk/android/v5/offline-send). +Deleting a group needs a connection — with offline support enabled, `deleteGroup()` is **not** queued for later. After it succeeds, the group's conversation and cached messages are removed from the on-device cache when the group-deleted event arrives or at the next reconnect sync; any messages still queued to it are then reported as failed with reason `DISCARDED`. See [Send While Offline](/sdk/android/v5/offline-send). From 3742378d0b671baec557c8d06fba01083ae46742 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 19:13:02 +0530 Subject: [PATCH 38/42] docs(android-v5): campaigns offline section no longer parents the fields table Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/campaigns.mdx | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/sdk/android/v5/campaigns.mdx b/sdk/android/v5/campaigns.mdx index 8eef05153..5364eefcb 100644 --- a/sdk/android/v5/campaigns.mdx +++ b/sdk/android/v5/campaigns.mdx @@ -340,20 +340,6 @@ request.fetchNext(object : CometChat.NotificationFeedFetchListener() { Call `fetchNext()` repeatedly for pagination. When the server has no more items, subsequent calls return an empty list. -## Offline Behaviour - -With offline support enabled, `NotificationFeedRequest.fetchNext()` on a `NotificationFeedFetchListener` first delivers `onCachedNotificationFeedItems` from the device (empty when nothing was recorded), then `onServerNotificationFeedItems` with the live page to reconcile by item `id`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). - -| Builder method | Offline | -|---|---| -| `setLimit` | Cached | -| `setReadState` | Cached | -| `setCategory` | Cached | -| `setChannelId` | Cached | -| `setTags` | Cached | -| `setDateFrom` | Cached | -| `setDateTo` | Cached | - ### NotificationFeedItem Fields | Field | Type | Description | @@ -370,6 +356,22 @@ With offline support enabled, `NotificationFeedRequest.fetchNext()` on a `Notifi --- +## Offline Behaviour + +With offline support enabled, `NotificationFeedRequest.fetchNext()` on a `NotificationFeedFetchListener` first delivers `onCachedNotificationFeedItems` from the device (empty when nothing was recorded), then `onServerNotificationFeedItems` with the live page to reconcile by item `id`. Offline, the read ends with `onError` with code `ERROR_INTERNET_UNAVAILABLE` after the cached page. See [Show Cached Data](/sdk/android/v5/offline-cached-data). + +A `fetchNext()` call made while another fetch of the same request is still running ends with `onError` with code `ERROR_REQUEST_IN_PROGRESS`. Once the list has ended, a further `fetchNext()` asks the server again and delivers only items this request has not delivered before. + +| Builder method | Offline | +|---|---| +| `setLimit` | Cached | +| `setReadState` | Cached | +| `setCategory` | Cached | +| `setChannelId` | Cached | +| `setTags` | Cached | +| `setDateFrom` | Cached | +| `setDateTo` | Cached | + ## Retrieve Notification Categories Use `NotificationCategoriesRequest` to fetch available categories for filter chips. From 11ec52b764baec9f6a4cff334f3c4e7cbdd99fc6 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 19:30:14 +0530 Subject: [PATCH 39/42] =?UTF-8?q?docs(android-v5):=20release=20review=20fi?= =?UTF-8?q?xes=20=E2=80=94=20SCHEMA=5FRESET=20reason,=20two=20broken=20anc?= =?UTF-8?q?hors,=20Groovy=20fence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-encryption.mdx | 4 ++-- sdk/android/v5/offline-reference.mdx | 2 +- sdk/android/v5/offline-sync.mdx | 1 + sdk/android/v5/setup.mdx | 2 +- sdk/android/v5/upload-files.mdx | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sdk/android/v5/offline-encryption.mdx b/sdk/android/v5/offline-encryption.mdx index 043d783b2..85e47708f 100644 --- a/sdk/android/v5/offline-encryption.mdx +++ b/sdk/android/v5/offline-encryption.mdx @@ -33,7 +33,7 @@ status.leakage // what is never covered (media files on disk) Offline support keeps the logged-in user's messages in a local database on the device. Turn encryption on to store that database **encrypted at rest** using [SQLCipher](https://www.zetetic.net/sqlcipher/). Encryption is **opt-in** and off by default; nothing changes until you call `encryption(true)`. The minimum SDK version is **5.1.0**. -Encryption covers the **message database only**. Downloaded media files (photos, voice notes, documents) stay as ordinary unencrypted files on disk, because their local paths are handed to native image and video loaders. See [What is and isn't encrypted](#what-is-and-isn-t-encrypted). +Encryption covers the **message database only**. Downloaded media files (photos, voice notes, documents) stay as ordinary unencrypted files on disk, because their local paths are handed to native image and video loaders. See [What is and isn't encrypted](#what-gets-encrypted). ## Add the SQLCipher dependency @@ -135,7 +135,7 @@ Log.d(TAG, "enabled=${status.isEnabled}" + The single most useful support signal is `isEnabled() == true` with `isCipherAvailable() == false`: encryption was switched on but could not run. On Android that almost always means the `net.zetetic:sqlcipher-android` dependency was never added to the app. -## What is and isn't encrypted +## What gets encrypted | Data | Encrypted at rest? | |---|---| diff --git a/sdk/android/v5/offline-reference.mdx b/sdk/android/v5/offline-reference.mdx index 2527394ab..d99fd0633 100644 --- a/sdk/android/v5/offline-reference.mdx +++ b/sdk/android/v5/offline-reference.mdx @@ -134,7 +134,7 @@ Each delivers the cached page first (when offline is active and a user is logged | `MessageFailureReason` | `TRANSIENT` (retry likely helps), `PERMANENT` (retry won't help), `QUEUE_OVERFLOW` (evicted to admit a newer send), `QUEUE_FULL` (new send rejected, queue full), `BYTE_OVERFLOW` (media exceeded the byte cap), `DISCARDED` (removed before it could send) | | `SyncOutcome` | `CAUGHT_UP` (gap replayed exactly), `PARTIAL` (a page was ingested but more remains) | | `SyncState` | `IDLE`, `SYNCING` | -| `OfflineCacheResetReason` | `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, `ESCALATED` (unclassified failure recurred and was escalated), `ENCRYPTION_CHANGED` (the `encryption` setting changed between launches) | +| `OfflineCacheResetReason` | `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, `ESCALATED` (unclassified failure recurred and was escalated), `ENCRYPTION_CHANGED` (the `encryption` setting changed between launches), `SCHEMA_RESET` (the on-disk cache was in a format this SDK version cannot read, such as after rolling back to an older SDK) | | `UploadFileItemStatus.State` | `QUEUED`, `UPLOADING`, `UPLOADED`, `FAILED`, `REJECTED` | ## Model fields diff --git a/sdk/android/v5/offline-sync.mdx b/sdk/android/v5/offline-sync.mdx index 8fe482dd3..b6c0f5a34 100644 --- a/sdk/android/v5/offline-sync.mdx +++ b/sdk/android/v5/offline-sync.mdx @@ -297,6 +297,7 @@ CometChat.addOfflineCacheResetListener("RESET_LISTENER", object : OfflineCacheRe | `CORRUPTED` | The database file is corrupt on disk. | | `ESCALATED` | An unclassified failure recurred across launches and was escalated to a permanent rebuild. | | `ENCRYPTION_CHANGED` | The `encryption` setting changed, so the on-disk cache was rebuilt in the configured format. | +| `SCHEMA_RESET` | The on-disk cache was written in a format this SDK version cannot read (for example, after rolling back to an older SDK), so it was rebuilt. Queued messages that could still be read are failed with `DISCARDED` first. | Remove the listener with `CometChat.removeOfflineCacheResetListener("RESET_LISTENER")`. See [Encrypt the Cache](/sdk/android/v5/offline-encryption) for the encryption-related reasons. diff --git a/sdk/android/v5/setup.mdx b/sdk/android/v5/setup.mdx index 1726e5a63..91e4636e9 100644 --- a/sdk/android/v5/setup.mdx +++ b/sdk/android/v5/setup.mdx @@ -45,7 +45,7 @@ Then, add CometChat to the **app level** `build.gradle` file in the `dependencie -```java +```groovy dependencies { implementation "com.cometchat:chat-sdk-android:5.1.0" } diff --git a/sdk/android/v5/upload-files.mdx b/sdk/android/v5/upload-files.mdx index d0634d3ca..cb2a6bb00 100644 --- a/sdk/android/v5/upload-files.mdx +++ b/sdk/android/v5/upload-files.mdx @@ -187,7 +187,7 @@ request.uploadAttachments(context, items, listener) | `uploadAttachment(context, fileId, uri, listener)` | Upload a single content `Uri` under `fileId`. | -**`fileId` is app-supplied and required.** The SDK does not generate one — you provide a stable id per file (echoed back unchanged on every event) so you can line each file up with its UI row. The app owns id uniqueness within the batch: **re-using a `fileId` replaces that entry** with a fresh queued upload (aborting the old one if it was in flight) — that is also the [retry path](#remove-retry--clear). `Uri`-backed items uploaded through an overload without a `Context` are rejected as invalid. +**`fileId` is app-supplied and required.** The SDK does not generate one — you provide a stable id per file (echoed back unchanged on every event) so you can line each file up with its UI row. The app owns id uniqueness within the batch: **re-using a `fileId` replaces that entry** with a fresh queued upload (aborting the old one if it was in flight) — that is also the [retry path](#remove-retry-and-clear). `Uri`-backed items uploaded through an overload without a `Context` are rejected as invalid. Validation, presigning, and the byte transfer all run **asynchronously** after `uploadAttachments()` returns — track outcomes through the listener. @@ -336,7 +336,7 @@ Query the request's current state at any time — useful when the user hits "sen The attachment getters (`getAttachment`, `getAttachments`, `getAttachmentsByType`) return **only uploaded** files, so they're safe to hand straight to `setAttachments()`. `getAttachmentCount()` counts every file regardless of state, so you can compare it against `getAttachments().size()` to see how many are still pending. -## Remove, retry & clear +## Remove, retry and clear From d0097ec8b1d9c57037cceff3313674c39d8bb911 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 1 Nov 2026 19:35:10 +0530 Subject: [PATCH 40/42] docs(android-v5): reactions real-time section uses MessageListener and ReactionEvent Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/reactions.mdx | 61 ++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/sdk/android/v5/reactions.mdx b/sdk/android/v5/reactions.mdx index 773bf71f1..bbbe98df2 100644 --- a/sdk/android/v5/reactions.mdx +++ b/sdk/android/v5/reactions.mdx @@ -266,22 +266,22 @@ The cached phase replays the page this request last received from the server onl ## Real-time Reaction Events -Keep the chat interactive with real-time updates for reactions. Register a listener for these events and make your UI responsive. +Keep the chat interactive with real-time updates for reactions. Reaction events arrive on the `MessageListener`: override `onMessageReactionAdded()` and `onMessageReactionRemoved()`. Each receives a `ReactionEvent`, whose `getReaction()` returns the `Reaction` and whose `getReceiverId()`, `getReceiverType()`, `getConversationId()` and `getParentMessageId()` identify where it happened. ```java private String listenerID = "UNIQUE_LISTENER_ID"; -CometChat.addMessageListener(listenerID, new CometChat.MessageReactionListener() { +CometChat.addMessageListener(listenerID, new CometChat.MessageListener() { @Override - public void onMessageReactionAdded(MessageReaction reaction) { - Log.e(TAG, "Reaction added"); + public void onMessageReactionAdded(ReactionEvent reactionEvent) { + Log.e(TAG, "Reaction added: " + reactionEvent.getReaction().getReaction()); } @Override - public void onMessageReactionRemoved(MessageReaction reaction) { - Log.e(TAG, "Reaction removed"); + public void onMessageReactionRemoved(ReactionEvent reactionEvent) { + Log.e(TAG, "Reaction removed: " + reactionEvent.getReaction().getReaction()); } }); ``` @@ -292,13 +292,13 @@ CometChat.addMessageListener(listenerID, new CometChat.MessageReactionListener() ```kotlin val listenerID = "UNIQUE_LISTENER_ID" -CometChat.addMessageListener(listenerID, object : CometChat.MessageReactionListener { - override fun onMessageReactionAdded(reaction: MessageReaction?) { - Log.e(TAG, "Reaction added") +CometChat.addMessageListener(listenerID, object : CometChat.MessageListener() { + override fun onMessageReactionAdded(reactionEvent: ReactionEvent) { + Log.e(TAG, "Reaction added: " + reactionEvent.reaction.reaction) } - override fun onMessageReactionRemoved(reaction: MessageReaction?) { - Log.e(TAG, "Reaction removed") + override fun onMessageReactionRemoved(reactionEvent: ReactionEvent) { + Log.e(TAG, "Reaction removed: " + reactionEvent.reaction.reaction) } }) ``` @@ -309,23 +309,19 @@ CometChat.addMessageListener(listenerID, object : CometChat.MessageReactionListe ## Removing a Reaction Listener -To stop listening for reaction events, remove the listener as follows: +To stop listening for reaction events, remove the listener with the same listener ID: ```java -private String listenerID = "UNIQUE_LISTENER_ID"; - -CometChat.removeMessageListener("reactionListener"); +CometChat.removeMessageListener(listenerID); ``` ```kotlin -val listenerID = "UNIQUE_LISTENER_ID" - -CometChat.removeMessageListener("reactionListener") +CometChat.removeMessageListener(listenerID) ``` @@ -380,11 +376,9 @@ for (reactionCount in message.reactions) { ## Update Message With Reaction Info -When a user adds or removes a reaction, you will receive a real-time event. Once you receive the real time event you would want to update the message with the latest reaction information. To do so you can use the `updateMessageWithReactionInfo()` method. - -The `updateMessageWithReactionInfo()` method provides a seamless way to update the reactions on a message instance (`BaseMessage`) in real-time. This method ensures that when a reaction is added or removed from a message, the BaseMessage object's getReactions() property reflects this change immediately. +When a user adds or removes a reaction, you will receive a real-time event. Once you receive it, you would want to update the message with the latest reaction information. To do so, use `CometChatHelper.updateMessageWithReactionInfo()`. -When you receive a real-time reaction event (`MessageReaction`), call the `updateMessageWithReactionInfo()` method, passing the `BaseMessage` instance (message), event data (MessageReaction) and reaction event action type (`CometChatConstants.REACTION_ADDED` or `CometChatConstants.REACTION_REMOVED`) that corresponds to the message being reacted to. +Pass the `BaseMessage` the reaction belongs to, the `Reaction` from the event (`reactionEvent.getReaction()`), and the action: `CometChatConstants.REACTION_ADDED` from `onMessageReactionAdded()` or `CometChatConstants.REACTION_REMOVED` from `onMessageReactionRemoved()`. It returns the message with its `getReactions()` updated. @@ -392,16 +386,16 @@ When you receive a real-time reaction event (`MessageReaction`), call the `updat // The message to which the reaction is related BaseMessage message = ...; -// The reaction event data received in real-time -MessageReaction messageReaction = ...; +// The reaction from the real-time ReactionEvent +Reaction reaction = reactionEvent.getReaction(); -// The recieved reaction event real-time action type. Can be CometChatConstants.REACTION_ADDED or CometChatConstants.REACTION_REMOVED +// CometChatConstants.REACTION_ADDED or CometChatConstants.REACTION_REMOVED String action = CometChatConstants.REACTION_ADDED; BaseMessage modifiedBaseMessage = CometChatHelper.updateMessageWithReactionInfo( -baseMessage, -messageReaction, -action + message, + reaction, + action ); ``` @@ -412,16 +406,15 @@ action // The message to which the reaction is related val message: BaseMessage = ... -// The reaction event data received in real-time -val messageReaction: MessageReaction = ... +// The reaction from the real-time ReactionEvent +val reaction: Reaction = reactionEvent.reaction -// The recieved reaction event real-time action type. Can be CometChatConstants.REACTION_ADDED or CometChatConstants.REACTION_REMOVED +// CometChatConstants.REACTION_ADDED or CometChatConstants.REACTION_REMOVED val action = CometChatConstants.REACTION_ADDED -// Update the BaseMessage instance with the new reaction information val modifiedBaseMessage: BaseMessage = CometChatHelper.updateMessageWithReactionInfo( - message, - messageReaction, + message, + reaction, action ) ``` From 2511893eaff2dca0e6a57699515d7903f1c328dc Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 2 Nov 2026 14:51:15 +0530 Subject: [PATCH 41/42] docs(android-v5): logout wipes the cache; thread flag is cached offline; full snapshot list Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/offline-overview.mdx | 2 +- sdk/android/v5/offline-reference.mdx | 2 +- sdk/android/v5/thread-subscription.mdx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/android/v5/offline-overview.mdx b/sdk/android/v5/offline-overview.mdx index a21ab0ca8..d327f8777 100644 --- a/sdk/android/v5/offline-overview.mdx +++ b/sdk/android/v5/offline-overview.mdx @@ -68,7 +68,7 @@ With offline support enabled, your fetch and send callbacks fire **twice** on th The SDK keeps a single local cache for the user who is logged in. -- Logging the **same** user back in reuses that cache, so the data is there instantly on next launch. +- The cache survives app restarts while the same user stays logged in, so the data is there instantly on next launch. - Logging out, a session expiring, or logging in as a **different** user wipes the cache, including any unsent messages still in the outbox, which are lost. You never clear it yourself — the SDK does it. See [Stay in Sync](/sdk/android/v5/offline-sync) for cache resets and logout behaviour. diff --git a/sdk/android/v5/offline-reference.mdx b/sdk/android/v5/offline-reference.mdx index d99fd0633..6adc38772 100644 --- a/sdk/android/v5/offline-reference.mdx +++ b/sdk/android/v5/offline-reference.mdx @@ -52,7 +52,7 @@ Build with `OfflineConfig.Builder`. Every field is pre-set to its default, so ca | `mediaCaching(MediaCachingMode)` | enum | `AUTO` | `null` → `AUTO` | Receive-side media mode. Fixed at init. | | `encryption(boolean)` | `boolean` | `false` | — | Encrypt the cache database at rest. Requires the `net.zetetic:sqlcipher-android` dependency in your app; without it, `encryptionFailureBehavior` decides what happens. See [Encrypt the Cache](/sdk/android/v5/offline-encryption). | | `encryptionFailureBehavior(EncryptionFailureBehavior)` | enum | `ONLINE_ONLY` | `null` → `ONLINE_ONLY` | What happens when encryption cannot run at all. | -| `maxSnapshotQueries(int)` | `int` | `100` | `>= 1`; `0`/negative → `100` | Most entity-list snapshots (users, groups, group members) kept for offline replay; LRU. | +| `maxSnapshotQueries(int)` | `int` | `100` | `>= 1`; `0`/negative → `100` | Most entity-list snapshots kept for offline replay, across users, groups, group members, banned members, reactions, the notification feed and threads with `setParticipatedByMe(false)`; LRU. | | `snapshotTtlDays(int)` | `int` | `7` | `>= 1`; `0`/negative → `7` | Days an entity-list snapshot is kept after it was first recorded. | Attach the config at init via `AppSettings.AppSettingsBuilder.setOfflineConfig(OfflineConfig)`, or enable with defaults via the `offline(boolean)` shorthand — see [Enable Offline Support](/sdk/android/v5/offline-enable). diff --git a/sdk/android/v5/thread-subscription.mdx b/sdk/android/v5/thread-subscription.mdx index 30d004474..012ae549b 100644 --- a/sdk/android/v5/thread-subscription.mdx +++ b/sdk/android/v5/thread-subscription.mdx @@ -42,9 +42,9 @@ Thread subscription builds on [Threaded Messages](/sdk/android/v5/threaded-messa ## How State Works -The SDK keeps **no subscription state of its own**. There is no cache and no thread listener to reconcile: +The SDK keeps **no subscription state of its own** beyond the flag on each message, and there is no thread listener to reconcile: -- Every message fetch asks the server for the flag, and it arrives on the message — read it with `BaseMessage.isThreadSubscribed()`. +- Every message fetch asks the server for the flag, and it arrives on the message — read it with `BaseMessage.isThreadSubscribed()`. With [offline support](/sdk/android/v5/offline-cached-data) on, the flag is also cached with the message, so a cached read returns the last known value and the server read then updates it. - `subscribeToThread()` and `unsubscribeFromThread()` call back when the server has accepted the change. **The callback is the acknowledgement** — there is no follow-up event. Your app owns the resulting UI state. That means you decide when to flip a toggle optimistically, and you decide what a thread's state is before you have fetched it. From 681e853c57c3777f56966856042f0c0eafe699c8 Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 2 Nov 2026 17:39:17 +0530 Subject: [PATCH 42/42] =?UTF-8?q?docs(android-v5):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20onMessagePending=20scope,=20Kotlin=20signatures,=20?= =?UTF-8?q?retry=20scope,=20listener=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - onMessagePending: 36 comments corrected to fire once the message is queued (not every send); refused/media-copy-failed sends fire only onError - Kotlin onCachedMessages/onServerMessages standardised to List (receive-messages, threaded-messages) - retryAllFailedMessages scope: retries transient failures; permanent ones skipped (discard via discardLocalMessage) - offline-cached-data: re-pointed the per-builder matrix to each request's own Offline Behaviour table - offline-send: autoSendPendingOnReconnect(false) now described as queued-then-TRANSIENT; manual-retry link to Retry and Discard - offline-sync: forceSyncNow recovery names only retryMessage for queued messages - offline-reference: listener type qualification note; getPendingMessages comment fixed (void + callback delivery) - Kotlin onError overrides made non-null for consistency Co-Authored-By: Claude Opus 5.5 --- sdk/android/v5/ai-moderation.mdx | 4 +- sdk/android/v5/authentication-overview.mdx | 2 +- sdk/android/v5/offline-cached-data.mdx | 4 +- sdk/android/v5/offline-enable.mdx | 2 +- sdk/android/v5/offline-media-caching.mdx | 4 +- sdk/android/v5/offline-reference.mdx | 8 ++- sdk/android/v5/offline-send.mdx | 14 ++--- sdk/android/v5/offline-sync.mdx | 6 +- sdk/android/v5/pin-conversation.mdx | 8 +-- sdk/android/v5/pin-message.mdx | 6 +- sdk/android/v5/reactions.mdx | 8 +-- sdk/android/v5/receive-messages.mdx | 32 +++++------ sdk/android/v5/save-message.mdx | 6 +- sdk/android/v5/send-message.mdx | 64 +++++++++++----------- sdk/android/v5/thread-subscription.mdx | 6 +- sdk/android/v5/threaded-messages.mdx | 8 +-- sdk/android/v5/upload-files.mdx | 4 +- 17 files changed, 95 insertions(+), 91 deletions(-) diff --git a/sdk/android/v5/ai-moderation.mdx b/sdk/android/v5/ai-moderation.mdx index 1436ea28e..6abf2c4d5 100644 --- a/sdk/android/v5/ai-moderation.mdx +++ b/sdk/android/v5/ai-moderation.mdx @@ -85,7 +85,7 @@ When you send a text, image, or video message, check the initial moderation stat CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(message: BaseMessage?) { @@ -110,7 +110,7 @@ When you send a text, image, or video message, check the initial moderation stat CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override diff --git a/sdk/android/v5/authentication-overview.mdx b/sdk/android/v5/authentication-overview.mdx index dcba763e4..33bbd4430 100644 --- a/sdk/android/v5/authentication-overview.mdx +++ b/sdk/android/v5/authentication-overview.mdx @@ -67,7 +67,7 @@ val authKey = "AUTH_KEY" if (CometChat.getLoggedInUser() == null) { CometChat.login(UID, authKey, object : CometChat.CallbackListener() { override fun onSuccess(user: User?) {} - override fun onError(e: CometChatException?) {} + override fun onError(e: CometChatException) {} }) } else { // user already logged-in perform your action diff --git a/sdk/android/v5/offline-cached-data.mdx b/sdk/android/v5/offline-cached-data.mdx index 3ff81c98c..6803ad77b 100644 --- a/sdk/android/v5/offline-cached-data.mdx +++ b/sdk/android/v5/offline-cached-data.mdx @@ -237,7 +237,7 @@ request.fetchNext(object : CometChat.UserFetchListener() { -For `ReactionsRequest`, use one request object per direction: an object caches only the first direction it fetches, and its cached phase stays empty in the other direction. Users, groups, members, banned members, reactions, and the notification feed are served from a per-request snapshot rather than a shared store; per-builder details live on each request's own page and in the [Offline Configuration & API Reference](/sdk/android/v5/offline-reference). +For `ReactionsRequest`, use one request object per direction: an object caches only the first direction it fetches, and its cached phase stays empty in the other direction. Users, groups, members, banned members, reactions, and the notification feed are served from a per-request snapshot rather than a shared store; per-builder details live in the **Offline Behaviour** table on each request's own page — [Retrieve Users](/sdk/android/v5/retrieve-users), [Retrieve Groups](/sdk/android/v5/retrieve-groups), [Retrieve Group Members](/sdk/android/v5/retrieve-group-members), [Block Users](/sdk/android/v5/block-users), and [Reactions](/sdk/android/v5/reactions). @@ -334,6 +334,6 @@ The most common server-only predicate is `searchKeyword`, which blanks the cache -An offline-critical screen must use only cache-supported filters — one innocuous builder flag can silently disable the cached phase for a whole screen. The full per-builder matrix of what the cache answers lives in the [Offline Configuration & API Reference](/sdk/android/v5/offline-reference). +An offline-critical screen must use only cache-supported filters — one innocuous builder flag can silently disable the cached phase for a whole screen. Each request's own page carries an **Offline Behaviour** table of exactly what its cache answers — [Conversations](/sdk/android/v5/retrieve-conversations), [Message filters](/sdk/android/v5/additional-message-filtering), [Users](/sdk/android/v5/retrieve-users), [Groups](/sdk/android/v5/retrieve-groups), [Group Members](/sdk/android/v5/retrieve-group-members), [Kick / Ban](/sdk/android/v5/group-kick-member), [Block Users](/sdk/android/v5/block-users), [Threads](/sdk/android/v5/thread-subscription), [Mentions](/sdk/android/v5/mentions), [Reactions](/sdk/android/v5/reactions), and [Campaigns](/sdk/android/v5/campaigns). diff --git a/sdk/android/v5/offline-enable.mdx b/sdk/android/v5/offline-enable.mdx index 4cd2e7bd7..dc9f6d6ac 100644 --- a/sdk/android/v5/offline-enable.mdx +++ b/sdk/android/v5/offline-enable.mdx @@ -128,7 +128,7 @@ CometChat.init(context, "APP_ID", appSettings, object : CometChat.CallbackListen Log.d(TAG, "Initialization completed successfully") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Initialization failed: ${e?.message}") } }) diff --git a/sdk/android/v5/offline-media-caching.mdx b/sdk/android/v5/offline-media-caching.mdx index 581fdd789..60ac4f78f 100644 --- a/sdk/android/v5/offline-media-caching.mdx +++ b/sdk/android/v5/offline-media-caching.mdx @@ -27,7 +27,7 @@ val cached: String? = CometChat.getCachedMediaPath(attachment) // null if not ca CometChat.downloadMedia(attachment, object : MediaCallbackListener { override fun onSuccess(localPath: String?) { } override fun onProgress(percent: Int) { } - override fun onError(e: CometChatException?) { } + override fun onError(e: CometChatException) { } }) // Observe background (AUTO) downloads @@ -183,7 +183,7 @@ CometChat.downloadMedia(attachment, object : MediaCallbackListener { // 0..100, best-effort. } - override fun onError(e: CometChatException?) { } + override fun onError(e: CometChatException) { } }) ``` diff --git a/sdk/android/v5/offline-reference.mdx b/sdk/android/v5/offline-reference.mdx index 6adc38772..c448c799d 100644 --- a/sdk/android/v5/offline-reference.mdx +++ b/sdk/android/v5/offline-reference.mdx @@ -25,7 +25,7 @@ CometChat.getOfflineEncryptionStatus() // OfflineEncryptionStatus CometChat.getSyncState() // SyncState CometChat.getLastSyncTimestamp() // Long (server epoch seconds) CometChat.getCacheSize() // Long (bytes) -CometChat.getPendingMessages(listener) // List +CometChat.getPendingMessages(listener) // void; delivers List to the listener CometChat.forceSyncNow() // or forceSyncNow(callback) CometChat.clearOfflineCache() ``` @@ -66,7 +66,7 @@ Attach the config at init via `AppSettings.AppSettingsBuilder.setOfflineConfig(O | `getOfflineEncryptionStatus()` | `OfflineEncryptionStatus` | What encryption at rest is actually doing. Compare its strings against the `OfflineEncryptionStatus.AT_REST_*` and `KEY_STORAGE_*` constants. | | `getPendingMessages(CallbackListener>)` | `void` | All queued/failed messages in the outbox. | | `retryMessage(String localMessageId, MessageSentListener)` | `void` | Retry one failed message by its local id. | -| `retryAllFailedMessages(MessageSentListener)` | `void` | Retry every failed message in the outbox. | +| `retryAllFailedMessages(MessageSentListener)` | `void` | Retry the failed messages whose failure was transient; permanent failures are skipped — discard them with `discardLocalMessage`. | | `discardLocalMessage(String localMessageId, CallbackListener)` | `void` | Remove one unsent message from the outbox. | | `forceSyncNow()` | `void` | Request a catch-up sync. Silently dropped if a sync is already running or one ran within the cooldown window, and a no-op when offline is not active. Use the callback overload to learn why a sync did not start. | | `forceSyncNow(CallbackListener)` | `void` | Same, with a callback that reports why a manual sync did not start. | @@ -91,6 +91,10 @@ On the returned `UploadFileRequest`, `getItems()` lists each file's `UploadFileI | `addOfflineCacheResetListener(String, OfflineCacheResetListener)` / `removeOfflineCacheResetListener(String)` | `OfflineCacheResetListener` | | `addMediaDownloadListener(String, MediaDownloadListener)` / `removeMediaDownloadListener(String)` | `MediaDownloadListener` | + +`MessageFetchListener`, `MessageSentListener`, `SyncListener` and `OfflineMessageListener` are nested abstract classes on `CometChat` (e.g. `CometChat.SyncListener`). `OfflineCacheResetListener` is a top-level abstract class in `com.cometchat.chat.core`. `MediaCallbackListener` and `MediaDownloadListener` are top-level interfaces in `com.cometchat.chat.models`. + + ## Listeners and callbacks ### Two-phase fetch listeners diff --git a/sdk/android/v5/offline-send.mdx b/sdk/android/v5/offline-send.mdx index 8ed24aec1..4aa87594d 100644 --- a/sdk/android/v5/offline-send.mdx +++ b/sdk/android/v5/offline-send.mdx @@ -14,7 +14,7 @@ import OfflineDeprecatedSend from '/snippets/sdk/offline-deprecated-send.mdx'; CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { /* status == PENDING, fires < 100ms */ } override fun onMessageSent(message: BaseMessage?) { /* server ack: real id + sentAt */ } - override fun onError(e: CometChatException?) { /* PENDING = queued, FAILED = failed */ } + override fun onError(e: CometChatException) { /* PENDING = queued, FAILED = failed */ } }) // Read offline state off a message @@ -95,7 +95,7 @@ CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { Log.d(TAG, "Sent: ${message?.id}") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { // Inspect the message status to tell "queued, not failed" from a real failure Log.e(TAG, "Send error: ${e?.code}") } @@ -133,14 +133,14 @@ CometChat.sendInteractiveMessage(interactiveMessage, listener) // InteractiveMes ## Queued, Not Failed -When the device is offline, the send still fires `onMessagePending`, then fires `onError` with the code `ERROR_INTERNET_UNAVAILABLE` for that attempt. This is a **notification, not a failure**: the message stays durably `PENDING`, and `onMessageSent` fires **later**, on reconnect, when it actually goes. This is the default behaviour, with `autoSendPendingOnReconnect` on. With it off, an offline send becomes `FAILED` (`TRANSIENT`) right away, and you recover it with `retryMessage`. +When the device is offline, the send still fires `onMessagePending`, then fires `onError` with the code `ERROR_INTERNET_UNAVAILABLE` for that attempt. This is a **notification, not a failure**: the message stays durably `PENDING`, and `onMessageSent` fires **later**, on reconnect, when it actually goes. This is the default behaviour, with `autoSendPendingOnReconnect` on. With it off, the send is still queued first (`onMessagePending` fires), then the immediate attempt fails as `FAILED` (`TRANSIENT`); recover it with `retryMessage`. So for a single send, `onError` and `onMessageSent` can **both** fire (offline, then reconnect), and `onError` does not always mean the message failed. Always read `message.getStatus()` to tell the two apart: | `status` after `onError` | Meaning | What to do | |---|---|---| | `PENDING` | Queued; not sent yet. Auto-sends on reconnect (with `autoSendPendingOnReconnect` on, the default). | Show as sending/queued; nothing to do. | -| `FAILED` | A real failure; will not retry on its own. | Offer the user a manual retry (see below). | +| `FAILED` | A real failure; will not retry on its own. | Offer the user a manual retry (see [Retry and Discard](#retry-and-discard)). | @@ -160,7 +160,7 @@ public void onError(CometChatException e) { ```kotlin -override fun onError(e: CometChatException?) { +override fun onError(e: CometChatException) { // textMessage is the message you passed to sendMessage; the SDK updates its status in place. when (textMessage.status) { MessageStatus.PENDING -> { /* Queued offline — auto-sends on reconnect */ } @@ -226,7 +226,7 @@ CometChat.getPendingMessages(object : CometChat.CallbackListener ${it.status}") } } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Could not read the outbox: ${e?.code}") } }) @@ -316,7 +316,7 @@ CometChat.retryAllFailedMessages(messageSentListener) CometChat.discardLocalMessage(localMessageId, object : CometChat.CallbackListener() { override fun onSuccess(discardedLocalId: String?) {} - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Discard rejected: ${e?.code}") } }) diff --git a/sdk/android/v5/offline-sync.mdx b/sdk/android/v5/offline-sync.mdx index b6c0f5a34..e7a8d0d95 100644 --- a/sdk/android/v5/offline-sync.mdx +++ b/sdk/android/v5/offline-sync.mdx @@ -23,7 +23,7 @@ val lastSync: Long = CometChat.getLastSyncTimestamp() // server epoch seconds // Trigger a manual catch-up (with an acceptance callback) CometChat.forceSyncNow(object : CometChat.CallbackListener() { override fun onSuccess(p: String?) { } - override fun onError(e: CometChatException?) { /* IN_PROGRESS / COOLDOWN / DISABLED / NOT_LOGGED_IN */ } + override fun onError(e: CometChatException) { /* IN_PROGRESS / COOLDOWN / DISABLED / NOT_LOGGED_IN */ } }) // Observe cache rebuilds @@ -185,7 +185,7 @@ CometChat.addMessageListener("MSG_LISTENER", object : CometChat.MessageListener( -On reconnect, queued messages are sent first, then the sync runs. `forceSyncNow` runs only the sync; it does not send queued messages (use `retryMessage` or `retryAllFailedMessages` for those). A message deleted on the server reaches you as an update on `onMessagesSynced`, not as a removal. +On reconnect, queued messages are sent first, then the sync runs. `forceSyncNow` runs only the sync; it does not send queued messages (use `retryMessage` for those). A message deleted on the server reaches you as an update on `onMessagesSynced`, not as a removal. ## Check sync state @@ -243,7 +243,7 @@ CometChat.forceSyncNow(object : CometChat.CallbackListener() { // Accepted and started. } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { // Rejected — see the code. } }) diff --git a/sdk/android/v5/pin-conversation.mdx b/sdk/android/v5/pin-conversation.mdx index b8869ce9d..cba93bfff 100644 --- a/sdk/android/v5/pin-conversation.mdx +++ b/sdk/android/v5/pin-conversation.mdx @@ -96,7 +96,7 @@ CometChat.pinConversation(UID, CometChatConstants.CONVERSATION_TYPE_USER, Log.d(TAG, "Conversation pinned at: ${conversation?.pinnedAt}") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Failed to pin conversation: ${e?.message}") } }) @@ -114,7 +114,7 @@ CometChat.pinConversation(GUID, CometChatConstants.CONVERSATION_TYPE_GROUP, Log.d(TAG, "Conversation pinned at: ${conversation?.pinnedAt}") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Failed to pin conversation: ${e?.message}") } }) @@ -159,7 +159,7 @@ CometChat.unpinConversation(UID, CometChatConstants.CONVERSATION_TYPE_USER, Log.d(TAG, "Conversation unpinned. isPinned: ${conversation?.isPinned}") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Failed to unpin conversation: ${e?.message}") } }) @@ -350,7 +350,7 @@ public void onError(CometChatException e) { ```kotlin -override fun onError(e: CometChatException?) { +override fun onError(e: CometChatException) { val limit = e?.errorParams?.get("limit") if (limit != null) { Log.e(TAG, "You can pin up to $limit conversations.") diff --git a/sdk/android/v5/pin-message.mdx b/sdk/android/v5/pin-message.mdx index 1f105f6d0..921a05f75 100644 --- a/sdk/android/v5/pin-message.mdx +++ b/sdk/android/v5/pin-message.mdx @@ -75,7 +75,7 @@ CometChat.pinMessage(messageId, object : CometChat.CallbackListener Log.d(TAG, "Message pinned at: ${message?.pinnedAt}") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Failed to pin message: ${e?.message}") } }) @@ -134,7 +134,7 @@ CometChat.unpinMessage(messageId, object : CometChat.CallbackListener ```kotlin -override fun onError(e: CometChatException?) { +override fun onError(e: CometChatException) { val limit = e?.errorParams?.get("limit") if (limit != null) { Log.e(TAG, "You can pin up to $limit messages in a conversation.") diff --git a/sdk/android/v5/reactions.mdx b/sdk/android/v5/reactions.mdx index bbbe98df2..922339271 100644 --- a/sdk/android/v5/reactions.mdx +++ b/sdk/android/v5/reactions.mdx @@ -44,7 +44,7 @@ CometChat.addReaction(messageId, emoji, object : CometChat.CallbackListener) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d( @@ -363,11 +363,11 @@ messagesRequest = MessagesRequestBuilder() .build() messagesRequest.fetchNext(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d( @@ -449,11 +449,11 @@ val messagesRequest = MessagesRequestBuilder() .build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -523,11 +523,11 @@ val messagesRequest = MessagesRequestBuilder() .build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -610,11 +610,11 @@ val messagesRequest = MessagesRequestBuilder() .build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -688,11 +688,11 @@ val messagesRequest = MessagesRequestBuilder() .build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -781,11 +781,11 @@ val UID: String = "cometchat-uid-1" val messagesRequest = MessagesRequestBuilder().setSearchKeyword("Hello").setUID(UID).setLimit(limit).build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") @@ -849,11 +849,11 @@ val GUID = "cometchat-guid-1" val messagesRequest = MessagesRequestBuilder().setSearchKeyword("Hello").setGUID(GUID).setLimit(limit).build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(list: List) { + override fun onServerMessages(list: List) { for (message in list) { if (message is TextMessage) { Log.d(TAG, "Text message received successfully: $message") diff --git a/sdk/android/v5/save-message.mdx b/sdk/android/v5/save-message.mdx index 5372c32da..7cc9b5c0a 100644 --- a/sdk/android/v5/save-message.mdx +++ b/sdk/android/v5/save-message.mdx @@ -72,7 +72,7 @@ CometChat.saveMessage(messageId, object : CometChat.CallbackListener ```kotlin -override fun onError(e: CometChatException?) { +override fun onError(e: CometChatException) { val limit = e?.errorParams?.get("limit") if (limit != null) { Log.e(TAG, "You can save up to $limit messages.") diff --git a/sdk/android/v5/send-message.mdx b/sdk/android/v5/send-message.mdx index eb18d0df9..4f9d0ed47 100644 --- a/sdk/android/v5/send-message.mdx +++ b/sdk/android/v5/send-message.mdx @@ -116,7 +116,7 @@ TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -144,7 +144,7 @@ val textMessage = TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(textMessage: BaseMessage?) { @@ -171,7 +171,7 @@ TextMessage textMessage = new TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -198,7 +198,7 @@ private val receiverType = CometChatConstants.RECEIVER_TYPE_GROUP val textMessage = TextMessage(receiverID, messageText, receiverType) CometChat.sendMessage(textMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(textMessage: BaseMessage?) { @@ -324,7 +324,7 @@ MediaMessage mediaMessage = new MediaMessage(receiverID, new File(filePath), mes CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -352,7 +352,7 @@ private val filePath = "/storage/emulated/0/Download/CometChat.jpg" val mediaMessage = MediaMessage(receiverID, File(filePath), messageType, receiverType) CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -380,7 +380,7 @@ MediaMessage mediaMessage = new MediaMessage(receiverID, new File(filePath), mes CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -408,7 +408,7 @@ private val filePath = "/storage/emulated/0/Download/CometChat.jpg" val mediaMessage = MediaMessage(receiverID, File(filePath), messageType, receiverType) CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -454,7 +454,7 @@ mediaMessage.setAttachment(attachment); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -487,7 +487,7 @@ mediaMessage.attachment = attachment CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage1: BaseMessage?) { @@ -519,7 +519,7 @@ mediaMessage.setAttachment(attachment); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -552,7 +552,7 @@ mediaMessage.attachment = attachment CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage1: BaseMessage?) { @@ -601,7 +601,7 @@ MediaMessage mediaMessage = new MediaMessage(receiverId, files, fileType, CometC CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -631,7 +631,7 @@ val mediaMessage = MediaMessage(receiverId, files, fileType, CometChatConstants. CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -661,7 +661,7 @@ MediaMessage mediaMessage = new MediaMessage(groupId, files, fileType, CometChat CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -692,7 +692,7 @@ val mediaMessage = MediaMessage(groupId, files, fileType, CometChatConstants.REC CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -743,7 +743,7 @@ mediaMessage.setAttachments(attachments); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -780,7 +780,7 @@ mediaMessage.attachments = attachments CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -817,7 +817,7 @@ mediaMessage.setAttachments(attachments); CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -854,7 +854,7 @@ mediaMessage.attachments = attachments CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(mediaMessage: BaseMessage?) { @@ -958,7 +958,7 @@ CustomMessage customMessage = new CustomMessage(UID, CometChatConstants.RECEIVER CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -988,7 +988,7 @@ val customMessage = CustomMessage(UID, CometChatConstants.RECEIVER_TYPE_USER, cu CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1018,7 +1018,7 @@ CustomMessage customMessage = new CustomMessage(GUID, CometChatConstants.RECEIVE CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1048,7 +1048,7 @@ val customMessage = CustomMessage(GUID, CometChatConstants.RECEIVER_TYPE_GROUP, CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1092,7 +1092,7 @@ customMessage.shouldUpdateConversation(false); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1124,7 +1124,7 @@ customMessage.shouldUpdateConversation(false) CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1155,7 +1155,7 @@ customMessage.shouldUpdateConversation(false); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1187,7 +1187,7 @@ customMessage.shouldUpdateConversation(false) CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1227,7 +1227,7 @@ customMessage.setConversationText("Custom Notification Body"); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1259,7 +1259,7 @@ customMessage.setConversationText("Custom Notification Body") CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { @@ -1290,7 +1290,7 @@ customMessage.setConversationText("Custom Notification Body"); CometChat.sendCustomMessage(customMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -1322,7 +1322,7 @@ customMessage.setConversationText("Custom Notification Body") CometChat.sendCustomMessage(customMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(customMessage: BaseMessage?) { diff --git a/sdk/android/v5/thread-subscription.mdx b/sdk/android/v5/thread-subscription.mdx index 012ae549b..5a058ac37 100644 --- a/sdk/android/v5/thread-subscription.mdx +++ b/sdk/android/v5/thread-subscription.mdx @@ -84,7 +84,7 @@ CometChat.subscribeToThread(parentMessageId, object : CometChat.CallbackListener Log.d(TAG, "Subscribed to thread: $response") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Failed to subscribe: ${e?.message}") } }) @@ -127,7 +127,7 @@ CometChat.unsubscribeFromThread(parentMessageId, object : CometChat.CallbackList Log.d(TAG, "Unsubscribed from thread: $response") } - override fun onError(e: CometChatException?) { + override fun onError(e: CometChatException) { Log.e(TAG, "Failed to unsubscribe: ${e?.message}") } }) @@ -225,7 +225,7 @@ CometChat.subscribeToThread(parentMessageId, object : CometChat.CallbackListener parentMessage.isThreadSubscribed = true } - override fun onError(e: CometChatException?) { } + override fun onError(e: CometChatException) { } }) ``` diff --git a/sdk/android/v5/threaded-messages.mdx b/sdk/android/v5/threaded-messages.mdx index e706d30ec..5a4e86684 100644 --- a/sdk/android/v5/threaded-messages.mdx +++ b/sdk/android/v5/threaded-messages.mdx @@ -197,11 +197,11 @@ val messagesRequest: MessagesRequest = neMessagesRequest.MessagesRequestBuilder( .build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List?) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(messages: List?) { + override fun onServerMessages(messages: List) { Log.d(TAG, "Messages for thread fetched successfully") } @@ -257,11 +257,11 @@ val messagesRequest: MessagesRequest = neMessagesRequest.MessagesRequestBuilder( .build() messagesRequest.fetchPrevious(object : CometChat.MessageFetchListener() { - override fun onCachedMessages(messages: List?) { + override fun onCachedMessages(messages: List) { // Render the cached page immediately. } - override fun onServerMessages(messages: List?) { + override fun onServerMessages(messages: List) { Log.d(TAG, "Messages for thread fetched successfully") } diff --git a/sdk/android/v5/upload-files.mdx b/sdk/android/v5/upload-files.mdx index cb2a6bb00..39704069e 100644 --- a/sdk/android/v5/upload-files.mdx +++ b/sdk/android/v5/upload-files.mdx @@ -429,7 +429,7 @@ UploadFileListener listener = new UploadFileListener() { CometChat.sendMediaMessage(mediaMessage, new CometChat.MessageSentListener() { @Override public void onMessagePending(BaseMessage message) { - // Fires on every send while offline support is enabled; message.getStatus() == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message.getStatus() == MessageStatus.PENDING } @Override @@ -490,7 +490,7 @@ val listener = object : UploadFileListener() { CometChat.sendMediaMessage(mediaMessage, object : CometChat.MessageSentListener() { override fun onMessagePending(message: BaseMessage?) { - // Fires on every send while offline support is enabled; message?.status == MessageStatus.PENDING + // Fires once the message is queued while offline support is enabled; message?.status == MessageStatus.PENDING } override fun onMessageSent(message: BaseMessage?) {