Files and images
im.files handles uploading and downloading files: uploading avatars and group files, or uploading first and building your own message; turning file URLs in messages into download URLs you can load, or downloading to the device; and listing and deleting group files. deeprespond_im_flutter also provides DRImage for showing images and VoiceRecorder for recording voice.
You don't need these methods to send image, voice, video or file messages. Pass the file straight to im.messages.send(), and the SDK handles preprocessing, uploading and sending, and resumes the upload even after the app is killed and restarted; see Messages. The methods on this page are for everything outside messages.
Rules for file purposes, size limits, retention and format checks are the same as on the server; see Files overview in the server docs.
| You want to | See |
|---|---|
| Upload avatars or group files, show progress, cancel | Upload a file, Cancel an upload |
| Preview a compressed image or get a video's cover before uploading | Use the preprocessing result on its own |
| Show images in messages | Show images with DRImage |
| Play video and voice, save files to the device | File URLs and download URLs, Download to the device |
| List, upload and delete group files | Group files |
| Hold to talk | Record voice |
Purposes and limits
Every upload must specify a purpose (constants in UploadPurpose), which decides the allowed types, the size limit and who can download it:
purpose | Constant | Used for | Allowed kind | Size limit | After upload you get |
|---|---|---|---|---|---|
attachment | UploadPurpose.attachment | Images, voice, video and files in messages | image, voice, video, file | Runtime policy max_upload_bytes (100 MB by default); images also at most 20 MB, voice at most 5 MB | A file URL, which must be exchanged for a download URL before loading |
user_avatar | UploadPurpose.userAvatar | The user's own avatar | image | 5 MB | A public URL that loads directly |
group_avatar | UploadPurpose.groupAvatar | Group avatar | image | 5 MB | A public URL |
chatroom_avatar | UploadPurpose.chatroomAvatar | Chat room cover | image | 5 MB | A public URL |
group_file | UploadPurpose.groupFile | Group files | image, video, file | Same as attachment | A file URL |
Constants for kind are in FileKind (FileKind.image, voice, video, file).
Before uploading, the SDK checks purpose, type and size locally. Files that don't qualify aren't uploaded, and the call rejects with local_validation: details['reason'] is too_large (with details['max_bytes'] as the limit) when the file is too big, or invalid_purpose when purpose and type don't match. The server also checks the app's storage quota and rejects with limit_exceeded (reason storage_quota) when it's full.
If the deployment has no file service (media_url_prefix in the runtime configuration is empty), uploads reject with unsupported (reason media_disabled).
Local files: DRFile
Files to upload or send are represented by DRFile, which has two sources:
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
// 本地文件的路径,如相册、文件选择器返回的路径
final photo = DRFile(pickedPath, mimeType: 'image/jpeg');
// 内存中的内容,如截图:必须给出文件名,SDK 先写入临时文件再上传
final shot = DRFile.fromBytes(pngBytes, name: 'screenshot.png', mimeType: 'image/png');- Path: the SDK only reads the file; it never modifies or deletes the original. A path that doesn't exist rejects with
local_validation(reasoninvalid_value,fieldfile). mimeType: optional, used to choose the default type (see below); if omitted, the type is detected from the start of the file.name: the file name, used whenfile-type files and group files are downloaded; defaults to the file name in the path.
Upload a file
im.files.upload() returns an upload task UploadTask immediately: result gives the FileInfo once the upload completes, progress emits upload progress, and cancel() cancels the upload.
final task = im.files.upload(
DRFile(pickedPath, mimeType: 'application/pdf'),
purpose: UploadPurpose.attachment,
kind: FileKind.file,
onProgress: (loaded, total) => debugPrint('${(loaded * 100 / total).round()}%'),
);
final info = await task.result;
debugPrint('${info.url} ${info.size}');- Type. If
kindis omitted andmimeTypeis given, it's taken from the MIME type's main category:image/*isimage,video/*isvideo,audio/*isvoice, and anything else isfile. WithoutmimeType, it's detected from the start of the file, and anything unrecognized isfile. - File name. If
nameis omitted, theDRFile's name is used. The SDK cleans the name the same way the server does: control characters,/,\and text-direction characters are removed, long names are cut to 255 characters, and an empty name becomes a default (such asfile.pdf). - Progress.
onProgress(loaded, total)andtask.progress(a broadcast stream that only gets progress after you subscribe, and closes when the upload ends) report bytes.totalis the size actually uploaded after preprocessing (a compressed image may be smaller than the original). Use either one. - In the returned
FileInfo,urlis the file URL (a public URL for avatars), and images also have a thumbnail URLthumbnailUrl. Rely on itssize,widthandheight.
Uploading only stores the file; it doesn't send a message or change any profile. To set avatars, see Users, friends and presence and Groups, for example calling im.users.updateMe(avatarUrl: info.url) after uploading.
Cancel an upload
Call the task's cancel(), or pass cancel: DRCancelToken and cancel the token. result fails with aborted, and the SDK also tells the server to cancel the upload.
final token = DRCancelToken();
final task = im.files.upload(DRFile(pickedPath), purpose: UploadPurpose.attachment, cancel: token);
// 用户点击“取消”时:task.cancel() 或 token.cancel()
task.cancel();
try {
await task.result;
} on DRException catch (e) {
if (e.code == LocalErrorCode.aborted) showToast('已取消');
}Large files and interruptions
- Files up to 32 MB are uploaded in one request. Larger files are uploaded in 16 MB parts, 3 at a time.
- File content goes straight to the storage service, not through the IM service's API, and the requests carry no IM credentials.
- Network interruptions. Each content upload retries 3 times on network errors. If the server reports missing content when completing the upload, the SDK re-uploads the missing parts and then completes. When the server is busy or rate-limited (with a wait of 60 seconds or less), the SDK waits and retries.
resultonly completes once there's a final result. - Going to the background or the app being killed. An
upload()runs only in memory and its state isn't saved. In the background it continues for as long as the system allows and may then fail when the app is suspended; after the app is killed it can't resume, so upload again. Unfinished uploads are cleaned up by the server once they expire (they don't count toward storage). Attachments sent with messages are different: they're kept in the send queue and resume where they stopped when the app restarts; see Messages.
Preprocessing before upload
For upload() and when sending messages, the SDK preprocesses files before uploading, using system capabilities: ImageIO and AVFoundation on iOS and macOS, BitmapFactory, ImageDecoder and MediaMetadataRetriever on Android; on Windows and Linux, images are processed in Dart (slower, run in a separate isolate).
| Content | Processing |
|---|---|
| Images | Rotated upright according to EXIF orientation; scaled down so the long side is at most 2048 pixels; re-encoded as JPEG (quality 85), or as PNG when there's an alpha channel. Re-encoded output has no EXIF or other metadata (such as location). GIFs aren't compressed. The long side and quality can be changed with MediaOptions |
Images (original: true) | JPEG, PNG, GIF and WebP aren't scaled or re-encoded; only JPEG EXIF (except orientation) and XMP, and PNG text chunks, are removed, and color profiles are kept. Formats the server doesn't accept (BMP, TIFF, AVIF and so on) are converted to JPEG or PNG without scaling |
| HEIC, HEIF | Converted to JPEG on iOS, macOS and Android 8 and later. Windows and Linux can't convert them, so they can't be uploaded as images |
| Video | Not transcoded. Location and similar metadata is removed from MP4 and MOV (a copy is modified when needed, never the original, and the size doesn't change). Width, height and duration are read, and the first frame is captured as a cover (not on Windows or Linux) |
| Voice | Not transcoded; only the format is checked |
| Files | Uploaded as is |
Formats are detected from the file's content, not its extension. The server accepts images in JPEG, PNG, GIF and WebP; video in MP4, MOV, WebM and 3GP; voice in AAC, M4A, MP3, AMR, 3GP, Ogg, WAV and WebM.
To change the image compression settings (when creating the client):
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
final client = await DRClient.create(DRClientOptions(
appKey: '1575529652#demo',
apiUrl: Uri.parse('https://im.example.com'),
platform: DRFlutterPlatform.instance,
media: const MediaOptions(imageMaxSide: 1600, jpegQuality: 80),
));In the following cases the file can't be uploaded as the requested type, and upload() rejects with local_validation. You decide whether to upload again with kind: FileKind.file (for attachments sent with messages, the SDK switches to a file message automatically):
reason | Case |
|---|---|
image_too_large | The image is over 50 megapixels, or still over 20 MB after processing |
unsupported_type | The server doesn't accept the format, such as MKV or AVI video, or HEIC that can't be converted |
Future<FileInfo> uploadImage(DRClient im, DRFile file) async {
try {
return await im.files.upload(file, purpose: UploadPurpose.attachment, kind: FileKind.image).result;
} on DRException catch (e) {
if (e.code == LocalErrorCode.localValidation && (e.reason == 'image_too_large' || e.reason == 'unsupported_type')) {
return im.files.upload(file, purpose: UploadPurpose.attachment, kind: FileKind.file).result; // 作为文件上传
}
rethrow;
}
}Attachments sent with messages
When sending images, voice, video and files with im.messages.send(), attachments follow the same rules, with these differences:
- Automatic switch to file messages. Images over 50 megapixels or still over 20 MB after processing, HEIC that can't be converted, and image, video and voice formats the server doesn't accept aren't rejected with
local_validation; they're sent asfilemessages automatically. - Keeping attachments. When writing to the send queue, the SDK copies the attachment into its own directory (content from
DRFile.fromBytesis written to a file), so you can delete the original oncesend()returns, and the upload resumes where it stopped after the app is killed and restarted. Attachments overOutboxOptions.persistAttachmentMaxBytes(200 MB by default) aren't copied; only the original file's path is recorded. If the original is deleted or moved before sending, the message fails withsend_abandoned(reasonattachment_lost), and the user needs to choose the file again. - Once sent, or after the message is deleted (
im.messages.discard()), the SDK deletes its copy.
final client = await DRClient.create(DRClientOptions(
appKey: '1575529652#demo',
apiUrl: Uri.parse('https://im.example.com'),
platform: DRFlutterPlatform.instance,
outbox: const OutboxOptions(persistAttachmentMaxBytes: 500 * 1024 * 1024), // 不超过 500 MB 的附件都复制
));For attachment fields in messages and the send methods, see Messages.
Use the preprocessing result on its own
im.files.prepare() only does the processing above, without uploading and without needing sign-in. Use it to preview a compressed image, get a video's cover and duration, or decide before uploading whether to send as a file:
final workDir = await Directory.systemTemp.createTemp('preview_');
try {
final prepared = await im.files.prepare(DRFile(pickedPath), FileKind.video, workDir: workDir.path);
if (prepared.kind != FileKind.video) {
showToast('这个视频格式不能在线播放,将作为文件发送');
} else {
debugPrint('${prepared.width}x${prepared.height},${prepared.durationSeconds} 秒,封面 ${prepared.thumbnailPath}');
}
} finally {
await workDir.delete(recursive: true); // 处理后的文件由你删除
}prepare()doesn't reject files that can't be processed as the requested type. Instead it suggests a type (file) in the result'skind, with the reason indowngraded.- Processed files are written to
workDir, or to a new directory in the system temp directory if omitted; delete it when you're done. For files that didn't need rewriting (such as unchanged video, voice and files),pathis the original file's path, so deleteworkDirrather thanpath. upload()doesn't upload a video's cover; if you need one, uploadthumbnailPathseparately withkind: FileKind.image.
Show and download files
File URLs and download URLs
URLs of message attachments and group files (file URLs) can't be loaded directly; exchange them for short-lived download URLs first. Public avatar URLs and your own external URLs can be used as is. im.files.isFileUrl(url) tells you whether a URL is a file URL that needs exchanging:
Future<String> playableUrl(DRClient im, String url) async {
if (!im.files.isFileUrl(url)) return url;
final resolved = await im.files.resolveUrl(url);
return resolved.downloadUrl; // 交给视频、音频播放器
}- Thumbnails. An image's thumbnail URL (the file URL plus
/thumb, i.e.thumbnailUrlinFileInfoandthumbnail_urlin an image message'sbody) also needs exchanging, and gives a thumbnail whose long side is at most 480 pixels. - Lifetime. Download URLs are valid for 60 to 90 minutes;
expiresAtis when they expire. The SDK caches them until then (in the local database, so they survive an app restart) and exchanges again when called within 5 minutes of expiry. Don't store download URLs or put them in messages; store the unchanging file URL and callresolveUrleach time you use it. - Batched requests. Exchanges within 20 ms are merged into one request (up to 100 each), so calling
resolveUrlfor each item in a list is fine. You can also useresolveUrlsto exchange several at once, with a result per item. - Each user can exchange up to 600 files per minute. When an item is rate-limited, the SDK retries it after 5 to 10 seconds.
- Download URLs for video and voice support
Rangerequests, so seeking works in players.
Error codes when an exchange fails:
code | Description | Suggested message |
|---|---|---|
file_expired | The file has expired or been deleted | File expired |
not_found | The URL is invalid, or the file was deleted long ago | File expired |
file_blocked | The file was blocked for violating the rules | File blocked |
not_group_member | A group file, and the user is no longer a member | You're no longer in this group |
final results = await im.files.resolveUrls(urls);
for (final r in results) {
final error = r.error;
if (error != null) {
debugPrint('${r.url}:${error.code == 'file_blocked' ? '文件已被屏蔽' : '文件已过期'}');
} else {
debugPrint('${r.url} → ${r.downloadUrl}');
}
}Show images with DRImage
DRImage is a Flutter ImageProvider: give it a file URL or thumbnail URL, and it exchanges for a download URL, downloads, and caches on disk by URL; URLs that aren't file URLs (such as external avatar URLs) are requested directly. Use it anywhere that accepts an ImageProvider (Image, CircleAvatar, DecorationImage):
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
import 'package:flutter/material.dart';
class ImageBubble extends StatelessWidget {
const ImageBubble({super.key, required this.im, required this.message});
final DRClient im;
final MessageView message;
@override
Widget build(BuildContext context) {
final body = message.body ?? const {};
final url = (body['thumbnail_url'] ?? body['url']) as String?; // 列表中先显示缩略图
if (url == null) return const SizedBox(width: 120, height: 120);
return Image(
image: DRImage(url, client: im),
width: 200,
fit: BoxFit.cover,
loadingBuilder: (context, child, progress) => progress == null ? child : const SizedBox(width: 200, height: 150, child: Center(child: CircularProgressIndicator())),
errorBuilder: (context, error, stack) => const Icon(Icons.broken_image),
);
}
}- Image content is cached in
deeprespond_im/imagesin the system cache directory. BeyondMediaOptions.imageCacheBytes(200 MB by default), least recently used images are evicted down to 80%. The same URL is only downloaded once at a time. - When a download fails (for example the file expired or was blocked),
errorBuildershows it, anderroris aDRException. DRImageCache.instance.clear()clears the whole cache; offer it as "Clear cache" on a settings page, or call it on sign-out.- Video covers and file previews can also be fetched as locally cached files with
DRImageCache.instance.fetch(im, url).
Download to the device
im.files.download() saves a file to the path you give, with progress and cancellation. File URLs are exchanged for download URLs first; other URLs are requested directly. Requests carry no credentials.
final token = DRCancelToken();
final saved = await im.files.download(
fileUrl,
'$downloadsDir/$fileName',
onProgress: (loaded, total) => debugPrint(total > 0 ? '${(loaded * 100 / total).round()}%' : '$loaded 字节'),
cancel: token,
);
debugPrint('已保存到 ${saved.path},${saved.size} 字节');- Content is written to
savePath.partfirst and renamed tosavePathwhen complete; on failure or cancellation the.partfile is deleted, so no incomplete file is left behind. The directory is created if it doesn't exist. - Where to save is up to you, such as the app documents directory from
path_provider; the SDK doesn't clean up downloaded files.
Group files
Group files are shared among group members. By default they don't expire (the app can set a retention period), and they're all deleted when the group is dissolved. Members can upload; the uploader, the owner and admins can delete. If the app turns off group files in its runtime policies (group_file_enabled is false in the runtime configuration), clients can't upload and upload() rejects with permission_denied (reason group_file_disabled).
// 上传:purpose 为 group_file,必须带 groupId;不能是语音
final info = await im.files
.upload(DRFile(pickedPath), purpose: UploadPurpose.groupFile, groupId: groupId, name: '会议纪要.pdf')
.result;
// 列表:按上传时间从新到旧
final page = await im.files.groupFiles(groupId, limit: 50);
debugPrint('共 ${page.fileCount} 个文件,${(page.usedBytes / 1024 / 1024).toStringAsFixed(1)} MB');
for (final f in page.items) {
debugPrint('${f.name} ${f.size} ${f.ownerNickname} ${f.createdAt}');
}
// 删除
await im.files.deleteGroupFile(groupId, info.fileId);- Each group can have up to 1000 group files. Only current members can view the list and download; after leaving the group, the user can no longer download.
- Group file changes aren't pushed in real time; query again when the group files page opens.
- Group file URLs also need exchanging for download URLs, or use
download().
Record voice
VoiceRecorder in deeprespond_im_flutter records with system capabilities (AVAudioRecorder on iOS, MediaRecorder on Android) straight to AAC (M4A container, mono, 64 kbps), which plays on every platform and can be sent directly as a voice message. Recording is only supported on iOS and Android; other platforms reject with unsupported.
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
// 用户按下按钮时
try {
await VoiceRecorder.start(); // 第一次使用时请求麦克风权限
} on DRException catch (e) {
if (e.code == LocalErrorCode.permissionRequired) {
showToast('请在系统设置中允许使用麦克风');
} else if (e.code == LocalErrorCode.deviceError) {
showToast('麦克风不可用');
}
return;
}
// 用户松开按钮时
final recording = await VoiceRecorder.stop();
await im.messages.send(SendTarget.user(peer), recording.toContent());
// 用户上滑取消时改为调用
// await VoiceRecorder.cancel();VoiceRecorder's methods are all static, and only one recording can run at a time: callingstart()while recording rejects withinvalid_state(reasonrecording), and callingstop()when not recording rejects withinvalid_state(not_recording).cancel()discards the recording and deletes the file, and does nothing when not recording.- Recordings are written to the system temp directory by default; set a directory with
start(directory: ...). Once sent, the SDK has its own copy in the send queue, so you can delete the recording file. - Recordings shorter than 1 second count as 1 second. The recorder doesn't limit duration, but voice files can't exceed 5 MB (about 10 minutes), so cap the recording length in your UI (for example 60 seconds).
- Instead of
toContent(), you can also build the content yourself:OutgoingContent.voice(DRFile(recording.path, mimeType: 'audio/mp4'), duration: recording.duration).
Microphone permission
Without permission, start() asks the user first; if the user declines, it rejects with permission_required and details['permissions'] is ['microphone']. Once the user chooses "Don't ask again", the system won't show the prompt again, so guide them to turn it on in system settings.
- iOS:
Info.plistmust state why the microphone is used, or the system terminates the app when it requests permission:
<key>NSMicrophoneUsageDescription</key>
<string>用于录制语音消息</string>- Android: the
RECORD_AUDIOpermission is declared indeeprespond_im_flutter's manifest and merged at build time; nothing else to configure.
API reference
im.files.upload()
Uploads a file, preprocessing it first as described in Preprocessing before upload.
| Parameter | Type | Required | Description |
|---|---|---|---|
file | DRFile | Yes | The file |
purpose | String | Yes (named) | Purpose, from UploadPurpose: attachment, user_avatar, group_avatar, chatroom_avatar, group_file |
kind | String? | No (named) | Type, from FileKind; taken from mimeType or the content by default. The three avatar purposes only allow image; group files can't be voice |
name | String? | No (named) | File name; defaults to the DRFile's name |
groupId | String? | Required for group files (named) | Group ID; not allowed for other purposes |
original | bool | No (named) | Don't compress images; only strip metadata. Default false |
onProgress | void Function(int loaded, int total)? | No (named) | Upload progress |
cancel | DRCancelToken? | No (named) | For cancelling |
Returns: UploadTask, whose result is a Future<FileInfo>.
Errors (result fails with DRException):
code | Description |
|---|---|
not_signed_in | Not signed in |
local_validation | reason: too_large over the size limit; invalid_purpose purpose and type don't match; required (field is group_id) group ID missing for a group file; invalid_value groupId given for another purpose, unknown purpose or kind, or the file doesn't exist; image_too_large, unsupported_type can't be uploaded as the requested type |
unsupported | The deployment has no file service (media_disabled) |
permission_denied | The app turned off group files (group_file_disabled) |
aborted | Cancelled |
limit_exceeded | The app's storage quota is full (storage_quota), or the group file limit is reached |
rate_limited | Too many uploads, with a wait longer than 60 seconds (usually the daily quota is used up) |
invalid_argument | Failed the server's checks, such as unsupported_type or invalid_image |
not_found, not_group_member | The group doesn't exist, or the user isn't a member |
storage_error | Reading or writing a local file failed, such as running out of temp space |
network_error, timeout | Still failing after automatic retries |
im.files.isFileUrl()
Whether a URL is a file URL (including thumbnail URLs) that needs exchanging for a download URL. Runs synchronously.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | String | Yes | URL |
Returns: bool. Always false when the deployment has no file service or before sign-in (no runtime configuration yet).
im.files.resolveUrl()
Exchanges a file URL for a download URL, caching the result until it expires.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | String | Yes | File URL or thumbnail URL |
Returns: Future<DownloadUrl>.
Errors: file_expired, file_blocked, not_found, not_group_member, not_signed_in, and network errors.
im.files.resolveUrls()
Exchanges several file URLs for download URLs, with a result per item.
| Parameter | Type | Required | Description |
|---|---|---|---|
urls | List<String> | Yes | File URLs |
Returns: Future<List<ResolvedUrl>> in request order.
im.files.download()
Downloads a file to the device. File URLs are exchanged for download URLs first; other URLs are requested directly (http and https only).
| Parameter | Type | Required | Description |
|---|---|---|---|
url | String | Yes | URL |
savePath | String | Yes | Where to save; an existing file is overwritten |
onProgress | void Function(int loaded, int total)? | No (named) | Download progress; total is 0 if the server doesn't give a size |
cancel | DRCancelToken? | No (named) | For cancelling |
Returns: Future<DownloadedFile>.
Errors: errors from exchanging the download URL; local_validation (savePath is empty, or the URL isn't http or https); not_found (the download URL returned 404), download_failed, internal (the storage service returned 5xx), network_error, aborted.
im.files.groupFiles()
Lists group files, newest upload first.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
cursor | String? | No (named) | Cursor for the next page, the previous page's nextCursor |
limit | int? | No (named) | Items per page; default 20, max 100 |
Returns: Future<GroupFilesPage>.
Errors: not_found, not_group_member.
im.files.deleteGroupFile()
Deletes a group file: the uploader, the owner or an admin. Succeeds if already deleted.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
fileId | String | Yes | File ID |
Returns: Future<void>.
Errors: permission_denied (not_uploader: not the uploader, owner or an admin), not_found, not_group_member, file_blocked (blocked files can't be deleted).
im.files.prepare()
Processes a file by the preprocessing rules, without uploading. Doesn't require sign-in.
| Parameter | Type | Required | Description |
|---|---|---|---|
file | DRFile | Yes | The file |
kind | String | Yes | The desired type, from FileKind |
original | bool | No (named) | Don't compress images; only strip metadata. Default false |
workDir | String? | No (named) | Directory for processed files; a new one in the system temp directory by default |
Returns: Future<PreparedFile>.
Errors: local_validation (unknown kind, or the file doesn't exist).
VoiceRecorder.start()
Static. Requests the microphone permission (if needed) and starts recording.
| Parameter | Type | Required | Description |
|---|---|---|---|
directory | String? | No (named) | Directory for the recording file; the system temp directory by default |
Returns: Future<void>.
Errors: unsupported (not iOS or Android; reason platform), invalid_state (recording: already recording), permission_required (the user didn't allow the microphone), device_error (the microphone is unavailable, for example in use by another app).
VoiceRecorder.stop()
Static. Stops recording and releases the microphone.
Returns: Future<VoiceRecording>.
Errors: invalid_state (not_recording: not recording), device_error.
VoiceRecorder.cancel()
Static. Discards the recording and deletes the file; does nothing when not recording.
Returns: Future<void>.
VoiceRecorder.isRecording
Static bool property: whether recording is in progress.
DRImageCache
DRImageCache.instance is the disk cache DRImage uses.
| Method | Description |
|---|---|
fetch(DRClient client, String url, {onProgress}) | Returns Future<File>: the cached file if present, otherwise downloads it first. Errors are the same as im.files.download() |
trim(int maxBytes) | Evicts least recently used images down to 80% when over maxBytes. The SDK calls it automatically with imageCacheBytes when downloading |
clear() | Clears the whole cache |
Types
DRFile
| Constructor / field | Type | Description |
|---|---|---|
DRFile(path, {mimeType, name}) | A local file | |
DRFile.fromBytes(bytes, {required name, mimeType}) | Content in memory | |
path | String? | File path |
bytes | Uint8List? | File content |
mimeType | String? | MIME type, used to choose the default type |
name | String? | File name |
FileInfo
File information after an upload completes. The original JSON is in raw.
| Field | Type | Description |
|---|---|---|
fileId | String | File ID |
purpose | String | Purpose |
kind | String | Type: image, voice, video, file |
status | String | Status; active once uploaded |
url | String | A file URL for message attachments and group files; a public URL for avatars |
thumbnailUrl | String? | Thumbnail URL; only for image attachments and image group files |
name | String? | File name |
contentType | String | Format detected by the server from the content, such as image/jpeg |
size | int | Size in bytes |
width | int? | Width of images and avatars, in pixels |
height | int? | Height of images and avatars, in pixels |
owner | String? | The uploader's username |
groupId | String? | The group a group file belongs to |
via | String | How it was uploaded; client for client uploads |
createdAt | DateTime? | Upload time; retention counts from here |
expiresAt | DateTime? | Expiry time; set for message attachments and for group files with a retention period |
GroupFile
A group file. In addition to the FileInfo fields:
| Field | Type | Description |
|---|---|---|
ownerNickname | String? | The uploader's nickname |
ownerAvatarUrl | String? | The uploader's avatar |
GroupFilesPage
| Field | Type | Description |
|---|---|---|
items | List<GroupFile> | This page's group files |
nextCursor | String? | Cursor for the next page; null if there isn't one |
fileCount | int | The group's total number of group files |
usedBytes | int | Bytes used by the group's files |
UploadTask
| Member | Type | Description |
|---|---|---|
result | Future<FileInfo> | The upload result; fails with aborted after cancellation |
progress | Stream<UploadProgress> | Upload progress (a broadcast stream), closed when the upload ends |
cancel() | void | Cancels the upload; no effect once it has finished |
UploadProgress has two fields, loaded and total (int, bytes).
PreparedFile
| Field | Type | Description |
|---|---|---|
path | String | The processed file to upload; the original file's path when no rewriting was needed |
kind | String | The suggested type; file if it can't be processed as the requested type |
name | String | The processed file name; the extension changes when an image is re-encoded |
format | String | Lowercase format name, such as jpeg, png, mp4; the extension for file |
contentType | String | MIME type |
size | int | Processed size in bytes |
width | int? | Width of images and video; null if it can't be read |
height | int? | Height of images and video |
durationSeconds | int? | Video duration in seconds |
thumbnailPath | String? | The video's first frame as a JPEG; not on Windows or Linux |
downgraded | String? | Why kind became file: image_too_large or unsupported_type |
DownloadUrl
| Field | Type | Description |
|---|---|---|
downloadUrl | String | Download URL; request it directly, with no credentials |
expiresAt | DateTime | When it expires (UTC) |
ResolvedUrl
| Field | Type | Description |
|---|---|---|
url | String | The requested file URL |
downloadUrl | String? | Download URL; null on failure |
expiresAt | DateTime? | When the download URL expires |
error | DRException? | The error on failure; see File URLs and download URLs |
DownloadedFile
| Field | Type | Description |
|---|---|---|
path | String | Where it was saved |
size | int | Bytes written |
contentType | String? | The Content-Type the server returned |
MediaOptions
DRClientOptions.media, given when creating the client.
| Field | Type | Default | Description |
|---|---|---|---|
imageMaxSide | int | 2048 | Long side of compressed images, in pixels |
jpegQuality | int | 85 | JPEG quality when re-encoding, 1 to 100 |
imageCacheBytes | int | 200 MB | Disk cache limit for DRImage, in bytes |
OutboxOptions
DRClientOptions.outbox, given when creating the client.
| Field | Type | Default | Description |
|---|---|---|---|
maxAge | Duration | 48 hours | How long unsent messages are kept; older ones fail with send_abandoned (expired) |
persistAttachmentMaxBytes | int | 200 MB | Attachments up to this size are copied into the SDK's directory; see Attachments sent with messages |
VoiceRecording
| Member | Type | Description |
|---|---|---|
path | String | The recording file (M4A) |
duration | Duration | Duration, at least 1 second |
toContent() | OutgoingContent | Content to send as a voice message |
