This is beta documentation for Stream Chat Flutter SDK v10. For the latest stable version, see the latest version (v9).

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.

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);
            },
          );
        },
      ),
    ];
  },
)

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

TypeDescription
TabbedAttachmentPickerOptionCustom inline UI shown as a tab (gallery, custom pickers)
SystemAttachmentPickerOptionLaunches 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,
)

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:

FieldDefaultDescription
mediaThumbnailSizeThumbnailSize(400, 400)Pixel dimensions of gallery thumbnails
mediaThumbnailFormatThumbnailFormat.jpegThumbnailFormat.jpeg or ThumbnailFormat.png
mediaThumbnailQuality100Quality value between 0–100
mediaThumbnailScale1Logical-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:

MethodDescription
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.