Skip to content

Improve attachment picker selection and staging#6233

Open
andremion wants to merge 20 commits intov7from
redesign/AND-1101-attachment-picker-tweaks
Open

Improve attachment picker selection and staging#6233
andremion wants to merge 20 commits intov7from
redesign/AND-1101-attachment-picker-tweaks

Conversation

@andremion
Copy link
Contributor

@andremion andremion commented Mar 10, 2026

Goal

Camera-captured attachments were silently dropped when the user subsequently selected items
from the gallery or file tabs. It was also impossible to combine camera captures with gallery
or file selections in a single message. This change fixes the picker so all attachment types
co-exist in the composer, insertion order is preserved, and allowMultipleSelection is respected
per tab.

Implementation

Root causes fixed:

  • AttachmentTypePicker's LaunchedEffect(modes) re-ran on every recomposition, resetting the
    active tab to the first one and discarding the user's current context.
  • updateSelectedAttachments(getSelectedAttachments()) rebuilt the composer list from only the
    current tab's selection, silently dropping camera attachments that arrived via a separate path.
  • AttachmentPickerActions lacked @Stable, so it was re-created on every recomposition.

Attachment staging redesign (MessageComposerController):

  • Replaced the flat List<Attachment> with a LinkedHashMap<String, Attachment> keyed by
    EXTRA_SOURCE_URI. All sources (camera, gallery, files) write to the same map, preserving
    insertion order and deduplicating by URI rather than by filename.
  • Renamed addSelectedAttachmentsaddAttachments, removeSelectedAttachment
    removeAttachment. Added removeAttachmentsByUris, clearAttachments. Removed updateSelectedAttachments.

AttachmentsPickerViewModel / AttachmentPickerActions (Compose):

  • Picker selection state moved from Set<Uri> to Set<String> (URI strings), persisted in
    SavedStateHandle so checkmarks survive process death (e.g. camera launch and return).
  • Item selection/deselection is now symmetric: toggling an item updates both the picker checkmark
    and the composer list atomically. In single-select mode (allowMultipleSelection = false),
    the displaced URI is removed from the composer before the new item is added.
  • @Stable added to AttachmentPickerActions; default instance wrapped with remember.

MessagesViewModelFactory / MessageComposerViewModel (Compose):

  • SavedStateHandle threaded through to both MessageComposerViewModel and
    AttachmentsPickerViewModel for full state survival across Activity recreation.
  • MessagesViewModelFactory simplified from a Map<Class<*>, () -> ViewModel> registry to
    a when expression.

XML SDK fix (AttachmentMetaDataMapper):

  • toAttachment() now stamps EXTRA_SOURCE_URI into extraData, fixing a regression where
    the XML in-app picker silently dropped all selected attachments.

XML sample: switched to in-app picker (streamUiMessageComposerAttachmentsPickerSystemPickerEnabled="false").

🎨 UI Changes

System picker In-app picker
Screen_recording_20260310_093908.webm
Screen_recording_20260310_094007.webm

Testing

  1. Camera + gallery: Capture a photo from the Camera tab, then switch to the Gallery tab and
    select an image. Verify both attachments appear in the composer.
  2. Camera + files: Capture a photo, then switch to the Files tab and select a file. Verify
    both appear in the composer.
  3. Deselect: Select a gallery image. Tap it again to deselect. Verify it is removed from
    the composer.
  4. XML in-app picker: In the XML sample, pick a file or image from the in-app picker. Verify
    it appears in the composer and sends correctly.

Summary by CodeRabbit

Release Notes

  • New Features

    • Attachments now persist across app restarts and configuration changes.
    • Added option to disable system attachment picker when preferred.
  • Bug Fixes

    • Improved attachment selection and removal handling in the message composer.
    • Fixed attachment picker state management to better reflect user selections.
  • Improvements

    • Enhanced attachment lifecycle management for more reliable attachment handling.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.70 MB 0.45 MB 🟡
stream-chat-android-ui-components 10.60 MB 11.00 MB 0.41 MB 🟡
stream-chat-android-compose 12.81 MB 12.04 MB -0.77 MB 🚀

@andremion andremion changed the title Attachment picker tweaks Fix attachment picker selection and staging Mar 10, 2026
@andremion andremion added pr:breaking-change Breaking change pr:bug Bug fix pr:improvement Improvement and removed pr:breaking-change Breaking change pr:bug Bug fix labels Mar 10, 2026
@andremion andremion changed the title Fix attachment picker selection and staging Improve attachment picker selection and staging Mar 10, 2026
@andremion andremion added the pr:ignore-for-release Exclude from release notes label Mar 10, 2026
@andremion andremion force-pushed the redesign/AND-1101-attachment-picker-tweaks branch from c61a36f to 57e9f9f Compare March 10, 2026 10:23
@andremion andremion marked this pull request as ready for review March 10, 2026 10:23
@andremion andremion requested a review from a team as a code owner March 10, 2026 10:23
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Walkthrough

This pull request refactors the attachment handling system across the Stream Chat Android SDK by renaming attachment-related APIs, integrating SavedStateHandle for state persistence across process death, transitioning from URI-based to string-based selection storage in the picker, and introducing new internal helper methods for attachment synchronization. The changes span ViewModels, the controller layer, UI components, and associated tests.

Changes

Cohort / File(s) Summary
ViewModel API Renaming
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/MessageComposer.kt, stream-chat-android-compose/src/main/kotlin/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt, stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt, stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt
Renamed addSelectedAttachmentsaddAttachments, removeSelectedAttachmentremoveAttachment; added new clearAttachments() method. Updated default lambda implementations and call sites.
MessageComposerViewModel SavedStateHandle Integration
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModel.kt, stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt
Added SavedStateHandle constructor parameter and persistence logic for attachment state; implements restoration on ViewModel creation and synchronized persistence during state changes.
AttachmentsPickerViewModel Selection Refactoring
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModel.kt
Transitioned from URI-based to string-based selection storage via _selectedUris: Set<String>; removed public methods toggleSelection, deselectAttachment, getSelectedAttachments, clearSelection; introduced internal selectItem, addToSelection, removeFromSelection, clearSelection variants; added SavedStateHandle persistence key KeySelectedUris.
AttachmentPickerActions & UI Component Updates
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentPickerActions.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentPicker.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentTypePicker.kt
Added @Stable annotation to AttachmentPickerActions; introduced internal helpers handlePickerItemSelection, handleItemSelection, consumePickerSession; wrapped actions parameter in remember for memoization; updated AttachmentTypePicker to conditionally auto-select first mode only when no pre-selection exists.
MessageComposerController Enhancement
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt
Added new attachment staging map _selectedAttachments keyed by EXTRA_SOURCE_URI; introduced selectedAttachments flow and public methods addAttachments, removeAttachment, removeAttachmentsByUris, clearAttachments; removed updateSelectedAttachments, addSelectedAttachments, removeSelectedAttachment.
ViewModelFactory SavedStateHandle Support
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/MessagesViewModelFactory.kt
Added create(modelClass, extras) override accepting CreationExtras to supply SavedStateHandle to ViewModels; introduced centralized createViewModel helper; updated attachment-related ViewModel instantiation to pass SavedStateHandle.
Attachment Metadata & Mapping
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/internal/AttachmentMetaDataMapper.kt
Added computation of extraData map containing EXTRA_SOURCE_URI for attachments to enable URI-based tracking and persistence.
Test Updates
stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModelTest.kt, stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModelTest.kt, stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerControllerTest.kt, stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/messages/MessageComposerViewModelTest.kt
Refactored to use renamed APIs (addAttachments, removeAttachment); updated test helpers to use explicit select/deselect instead of toggleSelection; added test coverage for removeAttachmentsByUris and clearAttachments; adjusted attachment creation to include EXTRA_SOURCE_URI in extraData.
Documentation & Sample Code
stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/*, stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/*, stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/*, stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/*, stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobot.kt, stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/AttachmentsTests.kt
Updated all code examples and test files to use renamed APIs; removed send parameter from UserRobot.uploadAttachment; updated attachment picker code snippets to use remember for action memoization and replaced getSelectedAttachments with getAttachmentsFromMetadata.
XML Layout & API Surface
stream-chat-android-ui-components-sample/src/main/res/layout/fragment_chat*.xml, stream-chat-android-compose/api/stream-chat-android-compose.api, stream-chat-android-ui-components/api/stream-chat-android-ui-components.api
Added app:streamUiMessageComposerAttachmentsPickerSystemPickerEnabled="false" attribute to MessageComposerView; updated API surface definitions to reflect method renames and new/removed methods across compose and UI component modules; documented constructor changes and new attachment management APIs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop along with changing tides,
Attachments now persist with pride,
Selection strings replace old URIs,
State survives, no more goodbyes,
APIs cleaner, names refined,
Persistence peace—what joy to find! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Improve attachment picker selection and staging' directly aligns with the main objective: fixing attachment picker behavior and redesigning attachment staging to preserve insertion order and prevent silent dropping of attachments.
Description check ✅ Passed The PR description comprehensively covers Goal, Implementation, UI Changes with video comparison, and Testing sections. It clearly explains root causes, architectural changes, and testing steps aligned with the template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch redesign/AND-1101-attachment-picker-tweaks

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt (1)

131-137: ⚠️ Potential issue | 🟠 Major

Add EXTRA_SOURCE_URI before staging this custom attachment.

addAttachments now only keeps attachments that resolve a non-null source URI in MessageComposerController.addAttachments (stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt:619-628). This sample creates a "date" attachment with only "payload" in extraData, so Line 137 will silently drop it and the selected date never reaches the composer. Please stamp a synthetic EXTRA_SOURCE_URI into extraData for this custom attachment before calling addAttachments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt`
around lines 131 - 137, The custom "date" Attachment being added via
composerViewModel.addAttachments is dropped because
MessageComposerController.addAttachments filters attachments without a non-null
source URI; before calling composerViewModel.addAttachments(listOf(attachment))
add a synthetic EXTRA_SOURCE_URI entry into the attachment's extraData
(Attachment.extraData) so it resolves to a non-null source URI; update the
Attachment constructed in MessagesActivity (where you create
Attachment(type="date", extraData=...)) to put the EXTRA_SOURCE_URI key with a
unique/stub URI string into extraData so the attachment is retained by
MessageComposerController.addAttachments.
stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/messages/MessageComposer.java (1)

413-433: ⚠️ Potential issue | 🟡 Minor

Update the inline sample comment to the renamed API.

The comment on Line 416 still says addSelectedAttachments(attachments), which now disagrees with the code and the published addAttachments(...) rename.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/messages/MessageComposer.java`
around lines 413 - 433, Update the inline comment in contentCustomization2():
change the comment that mentions addSelectedAttachments(attachments) to use the
new API name addAttachments(...). Locate DefaultMessageComposerLeadingContent
and the attachments button click listener (the lambda passed to
setAttachmentsButtonClickListener) and update the comment to reference
messageComposerViewModel.addAttachments(...) with appropriate placeholder args.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt (1)

3873-3877: ⚠️ Potential issue | 🟡 Minor

Finish the attachment-picker KDoc rename in this file.

This fixes one reference, but the earlier KDocs at Line 3636 and Line 3662 still point to ChatTheme.attachmentPickerConfig..., so IDE docs remain inconsistent after the config move.

As per coding guidelines, "Document public APIs with KDoc, including thread expectations and state notes"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt`
around lines 3873 - 3877, KDoc references to the moved attachment picker config
still point to ChatTheme.attachmentPickerConfig; update those docs to reference
the new property path ChatTheme.config.attachmentPicker (and specific flags like
ChatTheme.config.attachmentPicker.useSystemPicker where applicable) in
ChatComponentFactory.kt so all KDoc mentions reflect the config move and remain
consistent across the file.
stream-chat-android-ui-components/api/stream-chat-android-ui-components.api (1)

4514-4539: ⚠️ Potential issue | 🟠 Major

Add MessageComposerViewModel API changes to CHANGELOG.md.

The stream-chat-android-ui-components section in the UNRELEASED CHANGELOG is empty. Document the attachment API changes under ⚠️ Changed for v7:

  • addSelectedAttachments()addAttachments()
  • removeSelectedAttachment()removeAttachment()
  • updateSelectedAttachments() removed, clearAttachments() added

Since v7 allows breaking changes without deprecated overloads, explicit changelog documentation is the migration path for SDK consumers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@stream-chat-android-ui-components/api/stream-chat-android-ui-components.api`
around lines 4514 - 4539, The UNRELEASED CHANGELOG entry for
stream-chat-android-ui-components is empty; add a v7 "⚠️ Changed" entry
documenting the MessageComposerViewModel attachment API changes: list
addSelectedAttachments() → addAttachments(), removeSelectedAttachment() →
removeAttachment(), and note that updateSelectedAttachments() was removed and
clearAttachments() was added as the replacement; place this under the
stream-chat-android-ui-components section in CHANGELOG.md so consumers see the
migration path.
stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/AttachmentsTests.kt (2)

140-141: ⚠️ Potential issue | 🟡 Minor

Same issue: test step says "sends" but only attaches.

Similar to test_deleteImage, the step description says "WHEN user sends a file" but only uploadAttachment is called without tapOnSendButton().

🐛 Proposed fix
         step("WHEN user sends a file") {
             userRobot.uploadAttachment(type = AttachmentType.IMAGE)
+                .tapOnSendButton()
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/AttachmentsTests.kt`
around lines 140 - 141, The step description says "WHEN user sends a file" but
the test only calls userRobot.uploadAttachment(type = AttachmentType.IMAGE) and
never actually sends it; update the test to match the step by invoking the send
action after attaching (e.g., call the existing tapOnSendButton() helper or
equivalent on userRobot) so the flow mirrors test_deleteImage where
uploadAttachment is followed by tapOnSendButton().

77-92: ⚠️ Potential issue | 🟡 Minor

Test step description doesn't match the action performed.

Line 81 says "WHEN user sends an image" but uploadAttachment only attaches without sending. There's no tapOnSendButton() call, so the message is never actually sent before attempting to delete it. This may cause the test to fail or test unintended behavior.

🐛 Proposed fix
         step("WHEN user sends an image") {
             userRobot.uploadAttachment(type = AttachmentType.IMAGE)
+                .tapOnSendButton()
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/AttachmentsTests.kt`
around lines 77 - 92, The step description is misleading because
userRobot.uploadAttachment(type = AttachmentType.IMAGE) only attaches but does
not send; update test_deleteImage so after calling
userRobot.uploadAttachment(...) you call userRobot.tapOnSendButton() (or change
the step text to "attaches an image" if you want to keep it unsent); ensure the
sequence uses userRobot.tapOnSendButton() before userRobot.deleteMessage() so
the image message is actually sent and then deleted, and keep assertions
userRobot.assertImage(...) and userRobot.assertDeletedMessage() as-is.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt (1)

364-374: ⚠️ Potential issue | 🟠 Major

Only clear staged attachments after a successful send.

clearAttachments() now runs unconditionally right after sendMessage(). The send flow already owns attachment cleanup, so doing it here widens the reset to failure/validation paths as well and can drop staged attachments before the user can correct and retry. Please move this to the send success/result handler instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt`
around lines 364 - 374, The current onSendMessage handler clears staged
attachments unconditionally (composerViewModel.clearAttachments) immediately
after calling composerViewModel.sendMessage, which can drop attachments on
failure; remove the unconditional clearAttachments call from this lambda and
instead invoke composerViewModel.clearAttachments only when the send completes
successfully (move the call into the send success/result handler or observe the
send result from composerViewModel and clear attachments on success). Keep the
existing attachmentsPickerViewModel.setPickerVisible and clearSelection
behavior, but ensure attachment cleanup is triggered from the send completion
callback/flow associated with composerViewModel.sendMessage so
retries/validation failures keep staged attachments intact.
🧹 Nitpick comments (1)
stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/messages/AttachmentsPicker.kt (1)

106-111: Consider adding composerViewModel as a remember key.

The remember block references composerViewModel but only keys on attachmentsPickerViewModel. If composerViewModel changes (e.g., due to configuration change with different factory), the memoized actions would reference a stale ViewModel.

♻️ Suggested improvement
-                                actions = remember(attachmentsPickerViewModel) {
+                                actions = remember(attachmentsPickerViewModel, composerViewModel) {
                                     AttachmentPickerActions.defaultActions(
                                         attachmentsPickerViewModel,
                                         composerViewModel,
                                     )
                                 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/messages/AttachmentsPicker.kt`
around lines 106 - 111, The remember call that builds actions currently only
keys on attachmentsPickerViewModel but also captures composerViewModel, risking
stale references; update the remember invocation to include composerViewModel as
a key so the lambda recreates
AttachmentPickerActions.defaultActions(attachmentsPickerViewModel,
composerViewModel) whenever either attachmentsPickerViewModel or
composerViewModel changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@stream-chat-android-compose/api/stream-chat-android-compose.api`:
- Around line 4421-4423: Add a changelog entry under the
stream-chat-android-compose UNRELEASED CHANGELOG ⚠️ Changed section that
documents the MessageComposerViewModel attachment API migration: list the method
renames (addSelectedAttachments → addAttachments, removeSelectedAttachment →
removeAttachment, updateSelectedAttachments → clearAttachments) and describe the
new constructor signature change that MessageComposerViewModel now accepts a
androidx.lifecycle.SavedStateHandle parameter; include a short migration note
showing the old method names and their new equivalents and mention that
consumers must update calls and the ViewModel instantiation to pass
SavedStateHandle.

In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModelTest.kt`:
- Around line 380-435: Wrap each affected test in runTest and drain the test
scheduler before assertions: replace the plain `@Test` bodies in
AttachmentsPickerViewModelTest for the selection-state cases (the tests invoking
viewModel.select, viewModel.selectItem and asserting
viewModel.attachments.first().isSelected / last().isSelected) with a
kotlinx.coroutines.test.runTest { ... } block and call advanceUntilIdle() (or
equivalent test scheduler drain) after performing selection operations and
before the assertTrue/assertFalse/assertEquals checks to make the flow-backed
state deterministic; ensure the appropriate kotlinx.coroutines.test imports are
added.

---

Outside diff comments:
In
`@stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/AttachmentsTests.kt`:
- Around line 140-141: The step description says "WHEN user sends a file" but
the test only calls userRobot.uploadAttachment(type = AttachmentType.IMAGE) and
never actually sends it; update the test to match the step by invoking the send
action after attaching (e.g., call the existing tapOnSendButton() helper or
equivalent on userRobot) so the flow mirrors test_deleteImage where
uploadAttachment is followed by tapOnSendButton().
- Around line 77-92: The step description is misleading because
userRobot.uploadAttachment(type = AttachmentType.IMAGE) only attaches but does
not send; update test_deleteImage so after calling
userRobot.uploadAttachment(...) you call userRobot.tapOnSendButton() (or change
the step text to "attaches an image" if you want to keep it unsent); ensure the
sequence uses userRobot.tapOnSendButton() before userRobot.deleteMessage() so
the image message is actually sent and then deleted, and keep assertions
userRobot.assertImage(...) and userRobot.assertDeletedMessage() as-is.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt`:
- Around line 364-374: The current onSendMessage handler clears staged
attachments unconditionally (composerViewModel.clearAttachments) immediately
after calling composerViewModel.sendMessage, which can drop attachments on
failure; remove the unconditional clearAttachments call from this lambda and
instead invoke composerViewModel.clearAttachments only when the send completes
successfully (move the call into the send success/result handler or observe the
send result from composerViewModel and clear attachments on success). Keep the
existing attachmentsPickerViewModel.setPickerVisible and clearSelection
behavior, but ensure attachment cleanup is triggered from the send completion
callback/flow associated with composerViewModel.sendMessage so
retries/validation failures keep staged attachments intact.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt`:
- Around line 3873-3877: KDoc references to the moved attachment picker config
still point to ChatTheme.attachmentPickerConfig; update those docs to reference
the new property path ChatTheme.config.attachmentPicker (and specific flags like
ChatTheme.config.attachmentPicker.useSystemPicker where applicable) in
ChatComponentFactory.kt so all KDoc mentions reflect the config move and remain
consistent across the file.

In
`@stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/messages/MessageComposer.java`:
- Around line 413-433: Update the inline comment in contentCustomization2():
change the comment that mentions addSelectedAttachments(attachments) to use the
new API name addAttachments(...). Locate DefaultMessageComposerLeadingContent
and the attachments button click listener (the lambda passed to
setAttachmentsButtonClickListener) and update the comment to reference
messageComposerViewModel.addAttachments(...) with appropriate placeholder args.

In `@stream-chat-android-ui-components/api/stream-chat-android-ui-components.api`:
- Around line 4514-4539: The UNRELEASED CHANGELOG entry for
stream-chat-android-ui-components is empty; add a v7 "⚠️ Changed" entry
documenting the MessageComposerViewModel attachment API changes: list
addSelectedAttachments() → addAttachments(), removeSelectedAttachment() →
removeAttachment(), and note that updateSelectedAttachments() was removed and
clearAttachments() was added as the replacement; place this under the
stream-chat-android-ui-components section in CHANGELOG.md so consumers see the
migration path.

In
`@stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt`:
- Around line 131-137: The custom "date" Attachment being added via
composerViewModel.addAttachments is dropped because
MessageComposerController.addAttachments filters attachments without a non-null
source URI; before calling composerViewModel.addAttachments(listOf(attachment))
add a synthetic EXTRA_SOURCE_URI entry into the attachment's extraData
(Attachment.extraData) so it resolves to a non-null source URI; update the
Attachment constructed in MessagesActivity (where you create
Attachment(type="date", extraData=...)) to put the EXTRA_SOURCE_URI key with a
unique/stub URI string into extraData so the attachment is retained by
MessageComposerController.addAttachments.

---

Nitpick comments:
In
`@stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/messages/AttachmentsPicker.kt`:
- Around line 106-111: The remember call that builds actions currently only keys
on attachmentsPickerViewModel but also captures composerViewModel, risking stale
references; update the remember invocation to include composerViewModel as a key
so the lambda recreates
AttachmentPickerActions.defaultActions(attachmentsPickerViewModel,
composerViewModel) whenever either attachmentsPickerViewModel or
composerViewModel changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2e30dff2-25be-4b13-a57f-ba97751a8a13

📥 Commits

Reviewing files that changed from the base of the PR and between dcdd773 and 57e9f9f.

📒 Files selected for processing (34)
  • stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobot.kt
  • stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/AttachmentsTests.kt
  • stream-chat-android-compose/api/stream-chat-android-compose.api
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentPicker.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentPickerActions.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentTypePicker.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/MessageComposer.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModel.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModel.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/MessagesViewModelFactory.kt
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModelTest.kt
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModelTest.kt
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/MessagesViewModelFactoryTest.kt
  • stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/guides/AddingCustomAttachments.java
  • stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/messages/MessageComposer.java
  • stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/AddingCustomAttachments.kt
  • stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/messages/AttachmentsPicker.kt
  • stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/messages/MessageComposer.kt
  • stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/cookbook/ui/CustomComposerAndAttachmentsPicker.kt
  • stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/guides/AddingCustomAttachments.kt
  • stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/messages/MessageComposer.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerControllerTest.kt
  • stream-chat-android-ui-components-sample/src/main/res/layout/fragment_chat.xml
  • stream-chat-android-ui-components-sample/src/main/res/layout/fragment_chat_preview.xml
  • stream-chat-android-ui-components/api/stream-chat-android-ui-components.api
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/internal/AttachmentMetaDataMapper.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModelBinding.kt
  • stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/messages/MessageComposerViewModelTest.kt
  • stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt
  • stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/uicomponents/customattachments/MessagesActivity.kt

@andremion andremion enabled auto-merge (squash) March 10, 2026 11:48
@andremion andremion force-pushed the redesign/AND-1101-attachment-picker-tweaks branch from 9cd398e to eb0778c Compare March 10, 2026 14:00
private const val KeyBundleMimeType = "mimeType"
private const val AttachmentBundleSize = 5

private fun Attachment.toBundle(): Bundle = Bundle(AttachmentBundleSize).apply {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we ignore many attachment properties and in particular the custom data, wouldn't that be a problem if an integrator sets it in a custom attachment picker like in AddingCustomAttachmentsSnippet?

TBH I'm also not sure what's the alternative for the Map because we can't really serialize opaque data. I guess a partial mitigation would be to iterate through the entries and add to the bundle the ones we know are fine, like strings, ints, etc. But this doesn't address other data, which would still be lost.

Or maybe it's better to just rely just on memory for configuration changes (so we skip the bundle) and restart from an empty state on recreation after process death. Is the app consistently killed when capturing a picture? I'm surprised if that's the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for supporting activity restoration is that we had customers reporting issues because their users have low-end devices, which often reproduce scenarios of DKA (Don't Keep Activities).
I will reiterate on that code again to see if something different can be done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed some changes. I'm not 100% happy with the solution. Let me know WYT

Comment on lines +251 to +253
// Insertion-ordered map keyed by EXTRA_SOURCE_URI. Tracks picker selections
// independently of edit-mode attachments, so selections survive entering and exiting edit mode.
private val _selectedAttachments = MutableStateFlow(linkedMapOf<String, Attachment>())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm facing this in edit mode:

  • I can't remove attachments
  • Whenever I add a new attachment, the existing ones are cleared

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I will check that. Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed the fixes. Let me know if it's still reproducible

…e across process death and synchronize external attachments.

- Implement `SavedStateHandle` persistence for selected URIs, grid attachments, and external attachments (camera/system picker).
- Add `addExternalAttachments` and `removeExternalAttachment` to manage one-shot attachment sources.
- Update `getSelectedAttachments` to serve as the single source of truth for the message composer.
- Ensure `clearSelection` resets all persisted picker state.
…ckerViewModel` and `MessageComposerViewModel`.

- Shift responsibility for the master list of staged attachments from `AttachmentsPickerViewModel` to `MessageComposerViewModel`, making the latter the single source of truth for message attachments.
- Implement state persistence for selected attachments in `MessageComposerViewModel` using `SavedStateHandle` to survive Activity recreation.
- Update `AttachmentsPickerViewModel` to only manage grid selection state (URI-based checkmarks) and storage browsing.
- Refactor `AttachmentPickerActions` to coordinate selection between the picker (grid UI) and the composer (staged list).
- Rename attachment manipulation methods in `MessageComposerViewModel` to `addAttachments`, `removeAttachment`, and `clearAttachments`.
- Update `MessagesViewModelFactory` to provide `SavedStateHandle` to the composer and picker ViewModels.
…AttachmentsPickerViewModel`, `MessageComposerController`, and `MessageComposerViewModel`.
…in attachment metadata.

- Map the `uri` from `AttachmentMetaData` to `extraData` using the `EXTRA_SOURCE_URI` key.
- Ensure the source URI is preserved when converting metadata to an `Attachment` object.
…ViewModel` to `MessageComposerController` via `SavedStateHandle`.
- Introduce a dedicated state for audio recording attachments to ensure they are properly tracked and synced.
- Implement a fallback attachment key using name, MIME type, and file size when a source URI is missing.
- Update `removeAttachment` and `clearAttachments` to correctly handle picker selections, edit-mode attachments, and recording attachments.
- Ensure all attachment types are preserved and correctly merged during synchronization.
…o the text field is populated after process-death restore in edit mode.
@andremion andremion force-pushed the redesign/AND-1101-attachment-picker-tweaks branch from 931a76b to 0a56359 Compare March 13, 2026 16:47
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:ignore-for-release Exclude from release notes pr:improvement Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants