StreamMessageInput(
attachmentPickerOptionsBuilder: (context, defaultOptions) {
// Extend with a custom audio picker tab
return [
...defaultOptions,
TabbedAttachmentPickerOption(
key: 'audio-picker',
icon: Icons.audiotrack,
supportedTypes: [AttachmentPickerType.audios],
optionViewBuilder: (context, controller) {
return AudioPicker(
onAudioPicked: (audio) async {
await controller.addAttachment(audio);
},
);
},
),
];
},
)Attachment Picker Modal
Customizing the Attachment Picker
Introduction
The Attachment Picker is an inline panel that appears below the text input when a user taps the attachment button in StreamMessageInput. It provides built-in picker options per platform:
- Mobile — tabbed interface with Gallery, Files, Camera, Video, and Poll tabs.
- Web / Desktop — a system list picker with Image, Video, File, and Poll options.
Customizing Picker Options
Use attachmentPickerOptionsBuilder on StreamMessageInput to modify, filter, reorder, or extend the default options.
For custom attachment types that don't go through addAttachment (e.g. location, contact), use controller.notifyCustomResult() to signal completion. See Custom Attachment Results below.
Filter out specific tabs:
StreamMessageInput(
attachmentPickerOptionsBuilder: (context, defaultOptions) {
// Remove the poll tab
return defaultOptions.where((o) => o.key != 'poll-creator').toList();
},
)Reorder tabs:
StreamMessageInput(
attachmentPickerOptionsBuilder: (context, defaultOptions) {
return defaultOptions.reversed.toList();
},
)Option Types
| Type | Description |
|---|---|
TabbedAttachmentPickerOption | Custom inline UI shown as a tab (gallery, custom pickers) |
SystemAttachmentPickerOption | Launches a native system dialog (file browser, camera) |
Both types take icon: IconData (not a Widget).
Restricting Allowed Attachment Types
Use allowedAttachmentPickerTypes to limit which attachment types are available:
StreamMessageInput(
allowedAttachmentPickerTypes: [
AttachmentPickerType.images,
AttachmentPickerType.videos,
],
)Built-in types: AttachmentPickerType.images, .videos, .audios, .files, .poll.
Using the System Picker (Web/Desktop Style on Mobile)
Set useSystemAttachmentPicker: true to force the system list picker on mobile:
StreamMessageInput(
useSystemAttachmentPicker: true,
)Gallery Thumbnail Configuration
Thumbnail appearance in the gallery tab is configured via GalleryPickerConfig, which you pass when building a custom picker via tabbedAttachmentPickerBuilder or systemAttachmentPickerBuilder. The available fields are:
| Field | Default | Description |
|---|---|---|
mediaThumbnailSize | ThumbnailSize(400, 400) | Pixel dimensions of gallery thumbnails |
mediaThumbnailFormat | ThumbnailFormat.jpeg | ThumbnailFormat.jpeg or ThumbnailFormat.png |
mediaThumbnailQuality | 100 | Quality value between 0–100 |
mediaThumbnailScale | 1 | Logical-pixel scale factor |
Typed Errors
Attachment errors are strongly typed. Catch them from StreamAttachmentPickerController.addAttachment():
try {
await controller.addAttachment(attachment);
} on AttachmentTooLargeError catch (e) {
showError('File too large: ${e.fileSize} bytes (max ${e.maxSize} bytes)');
} on AttachmentLimitReachedError catch (e) {
showError('Maximum ${e.maxCount} attachments allowed');
}Surface these to the user via the onError parameter on StreamMessageInput:
StreamMessageInput(
onError: (error, stackTrace) {
if (error is AttachmentTooLargeError) {
showSnackBar('File too large');
} else if (error is AttachmentLimitReachedError) {
showSnackBar('Too many attachments');
}
},
)Custom Attachment Results
For custom picker tabs that produce non-media results (e.g. location, contact), use controller.notifyCustomResult() instead of addAttachment(). This emits a CustomAttachmentPickerResult that StreamMessageInput forwards to onAttachmentPickerResult.
Step 1 — Define your result type:
class LocationPicked extends CustomAttachmentPickerResult {
const LocationPicked({required this.location});
final LatLng location;
}Step 2 — Emit the result from the picker tab:
TabbedAttachmentPickerOption(
key: 'location-picker',
icon: Icons.location_on,
supportedTypes: [CustomLocationPickerType()],
optionViewBuilder: (context, controller) {
return LocationPicker(
onLocationPicked: (location) {
if (location == null) return; // user cancelled — do nothing
controller.notifyCustomResult(LocationPicked(location: location));
},
);
},
)Do not call Navigator.pop() from inside a picker tab — the picker is an inline widget, not a modal route, and popping would close the wrong page. notifyCustomResult is the correct signal.
Step 3 — Handle the result in onAttachmentPickerResult:
StreamMessageInput(
onAttachmentPickerResult: (result) {
if (result is LocationPicked) {
sendLocationMessage(result.location);
return true; // handled — StreamMessageInput will close the picker
}
return false; // not handled — let StreamMessageInput deal with it
},
)Return true to signal you handled the result (the picker closes automatically). Return false to let StreamMessageInput handle it.
onAttachmentPickerResult is only called for CustomAttachmentPickerResult subtypes. AttachmentsPicked, PollCreated, and AttachmentPickerError are handled internally by StreamMessageInput.
StreamAttachmentPickerController
The inline picker tracks selected attachments through StreamAttachmentPickerController. Key methods available inside an optionViewBuilder:
| Method | Description |
|---|---|
addAttachment(attachment) | Add a media attachment (image, video, file, audio) |
removeAttachment(attachment) | Remove a previously added attachment |
notifyCustomResult(result) | Emit a custom result (location, contact, etc.) to onAttachmentPickerResult |
StreamMessageInput manages its own internal controller automatically. You can configure limits via StreamMessageInput parameters:
StreamMessageInput(
maxAttachmentSize: 10 * 1024 * 1024, // 10 MB
attachmentLimit: 10,
)You only need to create a StreamAttachmentPickerController manually if you are embedding StreamTabbedAttachmentPicker or StreamSystemAttachmentPicker directly outside of StreamMessageInput.