Skip to main content
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(). 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).
Why upload separately instead of passing files to sendMediaMessage()?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.

The upload-then-send flow

1

Create a request

Call CometChat.createUploadFileRequest(receiverId, receiverType). The recipient is required — the server uses it to apply role- and scope-based access control before issuing upload URLs.
2

Upload

Call request.uploadAttachments(items, listener), where each UploadFileItem pairs a file with an app-supplied fileId (required) that is echoed back on every event, so you can map callbacks to your UI rows. Validation, presigning, and the byte transfer run asynchronously and report through the listener.
3

Track progress & handle failures

Your UploadFileListener receives onFileProgress per file, then onFileUploaded (success), onFileError (rejected — not retryable), or onFileFailure (failed — retryable). Use request.removeAttachment() or re-upload the same fileId as the user acts.
4

Collect attachments

Each onFileUploaded hands you an Attachment with a hosted URL. You can also read them from the request at any time with request.getAttachments() / request.getAttachmentsByType().
5

Build & send the message

Put the attachments on a MediaMessage with setAttachments(...) and call sendMediaMessage(). Because the attachments already have URLs, the message is sent as JSON — no re-upload.
6

Clean up

Call request.clearAll() after a successful send (or to abandon the composer) to release the batch from memory.

Create an upload request

createUploadFileRequest() accepts:
The recipient must match the message you’ll eventually send. receiverId / receiverType are sent to the upload-authorization (presign) endpoint, which enforces the sender’s role- and scope-based access control for that conversation before any bytes transfer. If the server declines a file (billing, plan, or content-type policy), it is rejected via onFileError — not retryable. Pass the same recipient you’ll set on the MediaMessage at send time.

Upload files

Build an UploadFileListener and call uploadAttachments() (or uploadAttachment() for a single file). Each UploadFileItem pairs a file with a required, app-supplied fileId. An item is backed by either a File or a content Uri — Uri-backed items (from the Android document/media pickers) need the Context overload so the SDK can resolve each Uri’s name, size, and mime type.
For files picked through the system pickers (content Uris), use the Context overloads:
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. 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.

The UploadFileListener

UploadFileListener is an abstract class — override only the callbacks you need. Unlike MessageListener or CallListener, it is not registered globally with a string id; it lives for the duration of the upload batch.
Rejected vs. failed — the key distinction. Exactly one of the two fires per non-successful file. onFileError (rejected) means the request itself is unacceptable — an invalid or unreadable file, the size or count limit breached, or a server-side authorization/policy denial at presign time. Retrying won’t help; the user must remove or replace the file. onFileFailure (failed) means a transient transport problem — network drop, a storage error, an expired upload URL, or a stalled upload. These can be retried by re-uploading the same fileId through the request.

UploadResult

onComplete receives the batch’s settled state. It fires each time the batch drains — including after more files are added and it drains again — and always reflects the whole batch’s current state, so treat it idempotently (recompute from result; set your Send-enabled flag, don’t toggle it).

Configuring the request

Chainable setters let you configure the batch before (or between) uploads:
Parallel uploads of large files over cellular data can saturate the connection and stall every file — raise setConcurrency only on known-fast networks (ideally network-aware: higher on Wi-Fi, 1 on cellular).
Each request owns exactly one batch. For separate destinations (e.g. a main conversation and a thread), create a separate UploadFileRequest for each — their file ids never cross.

Adding more files to the batch

To add files incrementally (e.g. the user picks more while earlier uploads are still running), just call uploadAttachments() again on the same request — they join the same batch, and onComplete refires when the batch next drains.

Per-call and global listeners

There are two listener scopes, and events fire on both (per-call first):
  • Per-call listener — the listener you pass to uploadAttachment() / uploadAttachments(). It receives events only for the files in that call.
  • Global batch listener — registered with request.addUploadListener(listener). It receives events for every file across all upload calls on the request. There is a single global slot: a later addUploadListener() replaces the previous one; removeUploadListener() clears it.

Reading the batch

Query the request’s current state at any time — useful when the user hits “send”:
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

There is no auto-clear on send or logout — call clearAll() yourself after a successful send (or when abandoning the composer) so the batch doesn’t linger in memory.

Send the uploaded files as a media message

Once your files are uploaded, collect their Attachments (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.
Build the message with the constructor that takes no filesMediaMessage(receiverId, messageType, receiverType) — when sending pre-uploaded attachments. If you also pass File objects, the SDK re-uploads them on send, which defeats the upload-first flow.
A message’s attachments should all match the message’s own type — send mixed picks as one message per kind (images → videos → audios → files) using getAttachmentsByType(). sendMediaMessage() also enforces the maximum attachments per message (see Limits); if a batch has more successful uploads than the limit allows, split them across multiple MediaMessages. See Multiple Attachments in a Media Message.

Limits

Two limits guard the flow, each read from your app settings (configured in the CometChat dashboard, with a built-in fallback): Read the current limits at runtime — useful for capping your picker’s selection and pre-validating file sizes before uploading:
An oversized or over-count file only rejects that file — the rest of the batch continues uploading. Removing a rejected file frees no capacity (rejected entries don’t consume it); removing a queued, failed, or uploaded file does.

Reliability behavior

The SDK handles a few transport edge cases for you:
  • Stalled uploads — if a file makes no progress for 30 seconds, its upload is aborted and reported through onFileFailure with ERR_UPLOAD_STALLED. Retry it by re-uploading the same fileId.
  • Expired upload URLs — each file’s pre-signed upload URL has a limited validity. If it expires before the transfer finishes, the file fails with ERR_PRESIGNED_URL_EXPIRED; re-uploading the same fileId requests a fresh URL automatically, so retries keep working even after a long delay.
  • Storage errors — if storage rejects the upload or the network drops, the failure surfaces through onFileFailure with ERR_S3_UPLOAD_FAILED and, where available, the transport’s own message.

Error handling

Every error delivered to onFileError / onFileFailure (and onError callbacks from sendMediaMessage()) is a CometChatException — read getCode() to branch:
Server-side rejections at presign time (e.g. policy or plan denials) carry the server’s own code and message where one is provided — ERR_PRESIGN_REJECTED is the fallback classification.

Next Steps

Send A Message

Send text, media, and custom messages

Multiple Attachments

Send several attachments in one media message

Receive Messages

Listen for incoming messages in real-time

Threaded Messages

Upload into a thread with setParentMessageId