Skip to content

Fix link preview resolution and skipEnrichUrl in message composer#6363

Merged
andremion merged 6 commits intov7from
fix-link-preview-resolver
Apr 14, 2026
Merged

Fix link preview resolution and skipEnrichUrl in message composer#6363
andremion merged 6 commits intov7from
fix-link-preview-resolver

Conversation

@andremion
Copy link
Copy Markdown
Contributor

@andremion andremion commented Apr 14, 2026

Goal

Fix two issues with link preview resolution in the message composer:

  1. Ghost preview after send — when the user sends a message before the link preview resolves, the in-flight enrichment completes and writes a stale preview into the already-cleared composer.
  2. Missing preview on sent messageskipEnrichUrl was derived from linkPreviews.isEmpty(), conflating "preview hasn't resolved yet" with "no preview wanted." This told the backend to skip enrichment when the user simply sent before the preview loaded, resulting in no link preview on the message.

Both fixes align the Android SDK with the iOS SDK's behavior.

Implementation

TestCoroutineExtension

  • Add CallDispatcherProvider.set/reset alongside the existing DispatcherProvider setup. This was a missing counterpart — Call.await() (via MapCall) was hopping to real Dispatchers.IO in tests.

MessageComposerController

  • Track linkPreviewJob and cancel it in both the debounce site (prevents duplicate in-flight calls) and clearData() (prevents ghost preview after send).
  • Replace the skipEnrichUrl = linkPreviews.isEmpty() derivation with an intent-based approach: a linkPreviewDismissed flag is set only when the user taps the dismiss button (cancelLinkPreview), and reset on any text change (setMessageInputInternal). At send time, shouldSkipEnrichUrl() combines the integrator override (message.skipEnrichUrl from ChannelScreen), user dismissal, and URL presence in the text.
  • Apply shouldSkipEnrichUrl() to both the new-message and edit-message paths.
  • Deduplicate setMessageInput by delegating to setMessageInputInternal.

MessageComposerControllerTest

  • Test that in-flight link preview resolution does not leak into state after clearData.
  • Test that skipEnrichUrl is false when sending before the preview resolves (backend should enrich).
  • Test that skipEnrichUrl is true after explicit preview dismissal.
  • Test that text changes reset the dismissal (backend should enrich again).

No UI changes.

Testing

  1. Open a channel in the Compose sample.
  2. Type a URL (e.g. www.meta.com), wait for the link preview to appear, then tap Send before the preview shows — verify the sent message has a link preview (backend enriched it).
  3. Type a URL, wait for the preview, tap the X to dismiss it, then Send — verify the message has no link preview.
  4. Type a URL, dismiss the preview, type a different URL, Send — verify the message has the new URL's preview.
  5. Type a URL, send before preview resolves — verify no ghost preview appears in the empty composer after send.

Summary by CodeRabbit

  • Bug Fixes
    • Improved link preview handling: dismissed previews are remembered, in-flight preview fetches are cancelled on input changes, and enrichment is skipped when appropriate.
  • Refactor
    • Composer UI now surfaces a single link preview (rather than a list), simplifying preview display.
  • Tests
    • Added tests covering preview timing, dismissal behavior, and skip-enrichment scenarios.

TestCoroutineExtension already overrides DispatcherProvider for deterministic tests but was missing the equivalent CallDispatcherProvider setup. This caused Call.await() (via MapCall) to hop to real Dispatchers.IO, making tests non-deterministic.
…reventing a resolved preview from appearing in an already-cleared composer after send.

- Replace skipEnrichUrl derivation from linkPreviews.isEmpty() with intent-based logic aligned with the iOS SDK: default to false (let backend enrich), only skip when the user explicitly dismisses the preview or the text contains no URLs. Integrator overrides from ChannelScreen are preserved.
@andremion andremion requested a review from a team as a code owner April 14, 2026 09:48
@andremion andremion added the pr:improvement Improvement label Apr 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 14, 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
Copy Markdown
Contributor

github-actions bot commented Apr 14, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.82 MB 0.57 MB 🔴
stream-chat-android-ui-components 10.60 MB 10.95 MB 0.36 MB 🟡
stream-chat-android-compose 12.81 MB 12.25 MB -0.57 MB 🚀

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

Walkthrough

The PR changes link-preview handling from a list to a single nullable preview, adds dismissal tracking and coroutine job lifecycle for preview enrichment, updates UI and state APIs accordingly, adjusts message sending to respect a new skip-enrichment decision, and configures call dispatchers in test setup/reset.

Changes

Cohort / File(s) Summary
Test Infrastructure
stream-chat-android-test/src/main/java/io/getstream/chat/android/test/TestCoroutineExtension.kt
Configure CallDispatcherProvider to use the test dispatcher in beforeAll and call CallDispatcherProvider.reset() in afterAll alongside existing dispatcher resets.
Composer Controller & State
stream-chat-android-ui-common/src/main/kotlin/.../MessageComposerController.kt, stream-chat-android-ui-common/src/main/kotlin/.../state/messages/composer/MessageComposerState.kt, stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
Replace list-based linkPreviews: List<LinkPreview> with a single linkPreview: LinkPreview?; add linkPreviewJob and dismissedLinkPreviewUrl tracking; introduce shouldSkipEnrichUrl() logic; update message preparation to set skipEnrichUrl based on new logic; update public API signatures to accept/return a single LinkPreview.
Compose UI adapters
stream-chat-android-compose/src/main/java/.../MessageInput.kt, .../MessageComposerInput*(...)
Consume the single messageComposerState.linkPreview (nullable) instead of a list; adjust rendering checks and preview props accordingly.
Tests
stream-chat-android-ui-common/src/test/kotlin/.../MessageComposerControllerTest.kt
Add tests for preview enrichment timing, dismissal behavior, and skipEnrichUrl outcomes; allow injecting an inherited coroutine scope in fixtures to simulate in-flight enrichment.
Docs sample
stream-chat-android-docs/src/main/java/.../ImplementingOwnCapabilities.java
Construct MessageComposerState using null for the link-preview parameter instead of an empty list in example code.
API surface (binary signatures)
stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
Updated generated API to remove list-based constructors/getters (getLinkPreviews) and add single-preview overloads/getter (getLinkPreview), and updated component7/copy signatures to the new type.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as MessageInput UI
  participant Controller as MessageComposerController
  participant Enricher as LinkPreviewEnricher
  participant Sender as MessageSender

  User->>UI: Type text with URL
  UI->>Controller: onInputChanged(debounced)
  Controller->>Enricher: start enrichUrl(url) (async) (linkPreviewJob)
  Enricher-->>Controller: enrich result (may be late)
  alt user cancels/dismisses before enrich completes
    User->>UI: dismiss/cancel preview
    UI->>Controller: cancelLinkPreview()
    Controller->>Controller: record dismissedLinkPreviewUrl, cancel linkPreviewJob, set linkPreview=null
    Enricher-->>Controller: (late) result ignored
  end
  User->>UI: hit send
  UI->>Controller: sendMessage()
  Controller->>Controller: shouldSkipEnrichUrl(message) -> true/false
  Controller->>Sender: send(message with skipEnrichUrl flag)
  Sender-->>Controller: ack
  Controller-->>UI: update state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nudged a link, it started to bloom,
A job and a URL danced in my room.
I waved my paw—dismissed with a tap,
Enrichment was canceled, avoided the trap.
Hooray for neat state, one preview to groom! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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 changes: fixing link preview resolution and skipEnrichUrl behavior in the message composer, which is the primary focus of the entire changeset.
Description check ✅ Passed The description provides comprehensive coverage of goals, implementation details, and testing instructions. While the UI Changes section is not populated (correctly, as there are no UI changes), all critical sections are well-documented.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-link-preview-resolver

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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.

@sonarqubecloud
Copy link
Copy Markdown

@andremion
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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-ui-common/api/stream-chat-android-ui-common.api (1)

2293-2305: Please make sure this MessageComposerState public API break is called out in migration notes.

This surface now exposes a single LinkPreview across constructors, component7, copy, and the getter, so SDK consumers using the old list-based model will need an explicit upgrade note.

Based on learnings: in major version branches like v7, breaking public API changes are allowed, but maintainers prefer documenting migration paths in MIGRATION_TO_V7.md/CHANGELOG.

Also applies to: 2321-2325, 2336-2336

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

In `@stream-chat-android-ui-common/api/stream-chat-android-ui-common.api` around
lines 2293 - 2305, Public API for MessageComposerState changed from exposing a
list of LinkPreview to a single LinkPreview across its constructors, component7,
copy, and getter; add a clear migration note in MIGRATION_TO_V7.md and CHANGELOG
calling out MessageComposerState (all its constructors and the synthetic
constructor), component7, copy, and the getter, explaining the type change
(List<LinkPreview> -> LinkPreview) and providing a short migration example (how
to pick/convert the old List<LinkPreview> to a single LinkPreview, e.g.,
firstOrNull or map to a single preview) plus any recommended runtime checks;
ensure the note references the exact symbol MessageComposerState and mentions
the affected methods so consumers can find and update usages.
🤖 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-ui-common/api/stream-chat-android-ui-common.api`:
- Around line 2293-2305: Public API for MessageComposerState changed from
exposing a list of LinkPreview to a single LinkPreview across its constructors,
component7, copy, and getter; add a clear migration note in MIGRATION_TO_V7.md
and CHANGELOG calling out MessageComposerState (all its constructors and the
synthetic constructor), component7, copy, and the getter, explaining the type
change (List<LinkPreview> -> LinkPreview) and providing a short migration
example (how to pick/convert the old List<LinkPreview> to a single LinkPreview,
e.g., firstOrNull or map to a single preview) plus any recommended runtime
checks; ensure the note references the exact symbol MessageComposerState and
mentions the affected methods so consumers can find and update usages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3cfbecde-78bc-4eb3-9190-0c9c6a76249d

📥 Commits

Reviewing files that changed from the base of the PR and between 964ccee and 19832a9.

📒 Files selected for processing (6)
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt
  • stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/guides/ImplementingOwnCapabilities.java
  • stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
  • 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/main/kotlin/io/getstream/chat/android/ui/common/state/messages/composer/MessageComposerState.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerControllerTest.kt

@andremion andremion merged commit 2431956 into v7 Apr 14, 2026
16 checks passed
@andremion andremion deleted the fix-link-preview-resolver branch April 14, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:improvement Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants