Skip to content

Introduce thread safe AsyncImageHeadersProvider for custom image headers#6203

Merged
aleksandar-apostolov merged 4 commits intodevelopfrom
feature/AND-1102_async_image_headers_provider
Mar 5, 2026
Merged

Introduce thread safe AsyncImageHeadersProvider for custom image headers#6203
aleksandar-apostolov merged 4 commits intodevelopfrom
feature/AND-1102_async_image_headers_provider

Conversation

@VelikovPetar
Copy link
Contributor

@VelikovPetar VelikovPetar commented Mar 3, 2026

Goal

The existing ImageHeadersProvider is synchronous and called on the main thread, blocking the UI for any non-trivial work (e.g. reading an encrypted auth token). This PR introduces AsyncImageHeadersProvider — a suspending, thread-safe alternative that is invoked inside Coil's interceptor pipeline on the IO dispatcher, making blocking or suspending operations safe.

Implementation

  • AsyncImageHeadersProvider (stream-chat-android-ui-common): New suspend interface with a single getImageRequestHeaders(url: String): Map<String, String> method. Always invoked on Dispatchers.IO.
  • ImageHeadersInterceptor (stream-chat-android-compose): A Coil Interceptor that wraps an AsyncImageHeadersProvider and injects its headers into every image request via the background interceptor chain.
  • StreamCoilImageLoaderFactory: Extended with a new imageLoader(context, interceptors) overload. The default factory (DefaultStreamCoilImageLoaderFactory) supports this out of the box; custom SAM-lambda factories fall back to the existing single-arg imageLoader(context) and will not receive the interceptors.
  • StreamImageLoaderFactory: Added a new constructor accepting a List<Interceptor> to prepend Coil interceptors to the component registry. Also removed the now-unnecessary custom OkHttp network fetcher setup (cache-control header + dispatcher config), simplifying the factory.
  • ChatTheme: Added asyncImageHeadersProvider: AsyncImageHeadersProvider? = null parameter. When non-null, ChatTheme builds the image loader with the ImageHeadersInterceptor injected. The old imageHeadersProvider parameter and ChatTheme.streamImageHeadersProvider accessor are now @Deprecated.

Testing

  • Pass a custom AsyncImageHeadersProvider implementation to ChatTheme(asyncImageHeadersProvider = ...) that injects an Authorization header.
  • Verify image requests (e.g. channel avatars, message attachments) include the expected header.
  • Verify the implementation is invoked off the main thread (e.g. log Thread.currentThread().name).
  • Confirm that using the deprecated imageHeadersProvider still works but emits a deprecation warning.

Summary by CodeRabbit

  • New Features

    • Added asynchronous image header injection support, enabling dynamic HTTP headers for image requests (e.g., authentication tokens).
    • ChatTheme now accepts asyncImageHeadersProvider parameter to supply custom headers during image loading.
  • Deprecations

    • ImageHeadersProvider deprecated in favor of asyncImageHeadersProvider for async header injection.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 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 3, 2026

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 🟢

@VelikovPetar VelikovPetar added the pr:new-feature New feature label Mar 3, 2026
@VelikovPetar VelikovPetar changed the title Introduce thread-safe AsyncImageHeadersProvider Introduce thread safe AsyncImageHeadersProvider for custom image headers Mar 3, 2026
@VelikovPetar VelikovPetar marked this pull request as ready for review March 3, 2026 15:28
@VelikovPetar VelikovPetar requested a review from a team as a code owner March 3, 2026 15:28
@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Walkthrough

The pull request introduces async HTTP header injection for image requests in Stream Chat Android. A new AsyncImageHeadersProvider interface enables dynamic header provision via a suspend function. The ChatTheme function now accepts an optional asyncImageHeadersProvider parameter that integrates with Coil's interceptor pipeline through a new internal ImageHeadersInterceptor. The image loader factory infrastructure was updated to support interceptor injection while maintaining backward compatibility.

Changes

Cohort / File(s) Summary
API Definitions
stream-chat-android-compose/api/stream-chat-android-compose.api, stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
Added public method signatures for imageLoader overloads accepting interceptor lists and new AsyncImageHeadersProvider interface public signature.
Async Header Provider Interface
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/AsyncImageHeadersProvider.kt
New public interface defining a suspend function getImageRequestHeaders(url: String) for async HTTP header provision on IO dispatcher, with guidance documentation distinguishing it from the deprecated sync ImageHeadersProvider.
Image Loader Factories
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory.kt, stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/StreamImageLoaderFactory.kt
Added constructor overloads accepting List<Interceptor> and optional builder lambda. Updated newImageLoader methods to register interceptors in Coil's component registry. Default implementations maintain backward compatibility.
Interceptor Integration
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/ImageHeadersInterceptor.kt
New internal Coil Interceptor class that intercepts image requests, retrieves headers asynchronously via AsyncImageHeadersProvider on IO dispatcher, and reinjects them into the request before chain proceeds.
Theme Configuration
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt
Added optional asyncImageHeadersProvider parameter to ChatTheme function. Updated imageLoader construction logic via remember to conditionally apply ImageHeadersInterceptor. Deprecated LocalStreamImageHeadersProvider in favor of async variant, with migration guidance in deprecation message.

Sequence Diagram

sequenceDiagram
    participant User as ChatTheme User
    participant Theme as ChatTheme
    participant Loader as ImageLoader
    participant Interceptor as ImageHeadersInterceptor
    participant Provider as AsyncImageHeadersProvider
    participant IO as IO Dispatcher

    User->>Theme: Provide asyncImageHeadersProvider
    Theme->>Loader: Build ImageLoader with interceptor
    
    User->>Loader: Request image (URL)
    activate Loader
    Loader->>Interceptor: Intercept request
    activate Interceptor
    Interceptor->>IO: Switch to IO dispatcher
    activate IO
    IO->>Provider: getImageRequestHeaders(url)
    activate Provider
    Provider-->>IO: Return headers Map
    deactivate Provider
    IO-->>Interceptor: Headers ready
    deactivate IO
    Interceptor->>Interceptor: Inject headers into request
    Interceptor->>Loader: Proceed with chain
    deactivate Interceptor
    Loader-->>User: Return image
    deactivate Loader
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Async headers hop through Coil's way,
Interceptors catch requests all day,
ChatTheme now springs with provider's grace,
Headers injected at the right place!
From IO threads they flow so bright,
Images load with headers so right! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% 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 'Introduce thread safe AsyncImageHeadersProvider for custom image headers' accurately describes the main feature added in this PR—a new async, thread-safe interface for providing custom headers.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering the goal, implementation details, and testing strategy.

✏️ 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 feature/AND-1102_async_image_headers_provider

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.

🧹 Nitpick comments (1)
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/StreamImageLoaderFactory.kt (1)

65-69: Defensively copy the interceptor list before storing it.

At Line 65, the factory keeps a reference to the caller-owned list. If that list is mutated later, loader behavior changes implicitly.

♻️ Suggested fix
     ) : this(builder) {
-        this.interceptors = interceptors
+        this.interceptors = interceptors.toList()
     }

     private var interceptors: List<Interceptor> = emptyList()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/StreamImageLoaderFactory.kt`
around lines 65 - 69, The factory currently stores a direct reference to the
caller-owned list in StreamImageLoaderFactory (the constructor assignment to
this.interceptors), which can be mutated externally; change the constructor to
defensively copy the provided interceptors list (e.g., assign a new
List/ArrayList created from the parameter) before storing it in the private var
interceptors to ensure internal immutability of StreamImageLoaderFactory's
interceptor state.
🤖 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/src/main/kotlin/io/getstream/chat/android/ui/common/images/StreamImageLoaderFactory.kt`:
- Around line 65-69: The factory currently stores a direct reference to the
caller-owned list in StreamImageLoaderFactory (the constructor assignment to
this.interceptors), which can be mutated externally; change the constructor to
defensively copy the provided interceptors list (e.g., assign a new
List/ArrayList created from the parameter) before storing it in the private var
interceptors to ensure internal immutability of StreamImageLoaderFactory's
interceptor state.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 29b2d88 and 62b9f48.

📒 Files selected for processing (7)
  • stream-chat-android-compose/api/stream-chat-android-compose.api
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/ImageHeadersInterceptor.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory.kt
  • 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/helper/AsyncImageHeadersProvider.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/StreamImageLoaderFactory.kt

Copy link
Contributor

@gpunto gpunto left a comment

Choose a reason for hiding this comment

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

Looks good. Left just a thought and a suggestion

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 4, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
38.5% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@aleksandar-apostolov aleksandar-apostolov merged commit b18ddb8 into develop Mar 5, 2026
28 of 32 checks passed
@aleksandar-apostolov aleksandar-apostolov deleted the feature/AND-1102_async_image_headers_provider branch March 5, 2026 12:33
@stream-public-bot stream-public-bot added the released Included in a release label Mar 6, 2026
@stream-public-bot
Copy link
Contributor

🚀 Available in v6.34.0

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

Labels

pr:new-feature New feature released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants