Skip to content

Fix CaptureMediaContract not remembered across recompositions#6169

Merged
VelikovPetar merged 1 commit into
developfrom
bug/AND-1072_fix_capture_media_contract_not_remembered_acress_compositions
Feb 19, 2026
Merged

Fix CaptureMediaContract not remembered across recompositions#6169
VelikovPetar merged 1 commit into
developfrom
bug/AND-1072_fix_capture_media_contract_not_remembered_acress_compositions

Conversation

@VelikovPetar
Copy link
Copy Markdown
Contributor

@VelikovPetar VelikovPetar commented Feb 19, 2026

Goal

Fix a bug where CaptureMediaContract was being created on every recomposition in rememberCaptureMediaLauncher, causing unnecessary re-registration of the activity result launcher.

Implementation

  • Wrap the CaptureMediaContract creation in a remember block keyed on photo and video parameters
  • Combine mode resolution and contract creation into a single remembered computation

🎨 UI Changes

No UI changes.

Testing

  1. Open a channel and tap the attachment picker
  2. Use the camera capture functionality
  3. Verify the camera launches correctly and captured media is handled properly

Summary by CodeRabbit

  • Refactor
    • Refactored media capture launcher initialization in the compose UI messaging component.

Co-Authored-By: Claude <noreply@anthropic.com>
@VelikovPetar VelikovPetar added the pr:bug Bug fix label Feb 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 19, 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.

@VelikovPetar VelikovPetar changed the title Fix CaptureMediaContract not remembered across recompositions Fix CaptureMediaContract not remembered across recompositions Feb 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.26 MB 5.26 MB 0.00 MB 🟢
stream-chat-android-offline 5.48 MB 5.48 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.63 MB 10.63 MB 0.00 MB 🟢
stream-chat-android-compose 12.85 MB 12.85 MB 0.00 MB 🟢

@sonarqubecloud
Copy link
Copy Markdown

@VelikovPetar VelikovPetar marked this pull request as ready for review February 19, 2026 14:42
@VelikovPetar VelikovPetar requested a review from a team as a code owner February 19, 2026 14:42
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2026

Walkthrough

Refactors CaptureMediaContract initialization by moving its construction from a direct value passed to rememberLauncherForActivityResult into a separate remember block keyed by photo and video parameters. Maintains identical observable behavior while optimizing contract object lifecycle management.

Changes

Cohort / File(s) Summary
Media Capture Contract Memoization
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/media/CaptureMediaLauncher.kt
Refactored CaptureMediaContract creation to use a remember block with photo and video keys, rather than constructing it inline within rememberLauncherForActivityResult.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A contract once born fresh each frame,
Now rests in remember's caring name,
With photo and video as its key,
More stable and wise this refactoring shall be!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title accurately summarizes the main change: fixing CaptureMediaContract not being remembered across recompositions, which directly matches the implementation.
Description check ✅ Passed The description covers the required Goal and Implementation sections clearly, though UI Changes and Testing sections could be more detailed.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug/AND-1072_fix_capture_media_contract_not_remembered_acress_compositions

Tip

Issue Planner is now in beta. Read the docs and try it out! 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
Copy Markdown

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

🧹 Nitpick comments (1)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/media/CaptureMediaLauncher.kt (1)

66-72: Consider restructuring to avoid a conditional composable call.

The ?: return null on line 68 means rememberLauncherForActivityResult is only called when contract is non-null. This is a conditional composable invocation: if photo/video transitions between (false, false) and a valid combination while this composable is in the tree, the slot for rememberLauncherForActivityResult is reset. While this was pre-existing and photo/video are unlikely to change at runtime, it's generally safer to always call rememberLauncherForActivityResult and return null after the fact, or to guard the call site so rememberCaptureMediaLauncher is only called when at least one mode is true.

An alternative that always invokes the composable:

♻️ Suggested restructure
 `@Composable`
 public fun rememberCaptureMediaLauncher(
     photo: Boolean,
     video: Boolean,
     onResult: (File) -> Unit,
 ): ManagedActivityResultLauncher<Unit, File?>? {
-    val contract = remember(photo, video) {
-        resolveMediaPickerMode(photo, video)?.let { CaptureMediaContract(it) }
-    } ?: return null
-    return rememberLauncherForActivityResult(contract) { file ->
+    val mode = remember(photo, video) { resolveMediaPickerMode(photo, video) }
+    val contract = remember(mode) { mode?.let { CaptureMediaContract(it) } }
+    val launcher = contract?.let {
+        rememberLauncherForActivityResult(it) { file ->
             file?.let(onResult)
         }
+    }
+    return launcher
 }
🤖 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/attachments/media/CaptureMediaLauncher.kt`
around lines 66 - 72, Compute val mode = resolveMediaPickerMode(photo, video)
via remember(photo, video) and always call rememberLauncherForActivityResult
with a non-null ActivityResultContract instance (e.g., construct a
CaptureMediaContract using mode when non-null, or pass a harmless dummy
CaptureMediaContract when mode is null) so rememberLauncherForActivityResult is
invoked unconditionally; then, after creating the launcher, return null if mode
is null, otherwise return the launcher that forwards results to onResult. Make
sure to reference resolveMediaPickerMode, CaptureMediaContract,
rememberLauncherForActivityResult, and the local contract/mode variable so the
composable call is not conditionally skipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/media/CaptureMediaLauncher.kt`:
- Around line 66-72: Compute val mode = resolveMediaPickerMode(photo, video) via
remember(photo, video) and always call rememberLauncherForActivityResult with a
non-null ActivityResultContract instance (e.g., construct a CaptureMediaContract
using mode when non-null, or pass a harmless dummy CaptureMediaContract when
mode is null) so rememberLauncherForActivityResult is invoked unconditionally;
then, after creating the launcher, return null if mode is null, otherwise return
the launcher that forwards results to onResult. Make sure to reference
resolveMediaPickerMode, CaptureMediaContract, rememberLauncherForActivityResult,
and the local contract/mode variable so the composable call is not conditionally
skipped.

@VelikovPetar VelikovPetar merged commit 04a2a58 into develop Feb 19, 2026
22 of 24 checks passed
@VelikovPetar VelikovPetar deleted the bug/AND-1072_fix_capture_media_contract_not_remembered_acress_compositions branch February 19, 2026 15:40
@stream-public-bot stream-public-bot added the released Included in a release label Feb 24, 2026
@stream-public-bot
Copy link
Copy Markdown
Contributor

🚀 Available in v6.32.4

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

Labels

pr:bug Bug fix released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants