Skip to content

Fix Firebase image-loading crash: use OkHttp Ktor engine explicitly - #3052

Merged
panasetskaya merged 1 commit into
developfrom
fix/coil-image-loading-crash-okhttp-engine
Aug 13, 2026
Merged

Fix Firebase image-loading crash: use OkHttp Ktor engine explicitly#3052
panasetskaya merged 1 commit into
developfrom
fix/coil-image-loading-crash-okhttp-engine

Conversation

@StylianosGakis

@StylianosGakis StylianosGakis commented Aug 13, 2026

Copy link
Copy Markdown
Member

The firebase crash as I understand originates from the fact that we were not explicitly using the latest Okhttp engine for ktor to use, so it was defaulting to a built-in one in Android which is not the latest and greatest.
This adds the engine explicitly which should make it use the latest properly.

AI description below:

What & why

Fixes the Crashlytics/Firebase crash "Exception in completion handler … Cancelling", whose true root cause (bottom of the causal chain) is:

java.lang.IllegalStateException: Unbalanced enter/exit
    at com.android.okhttp.okio.AsyncTimeout.enter
    ...
    at coil3.compose.AsyncImagePainter.onForgotten

Seen on production 14.4.2 (e.g. Pixel 7 / Android 17, Xiaomi 24115RA8EG / Android 16), both while the Chat screen was loading images.

Chain of events

  1. Coil forgets an AsyncImagePainter while an image request is in flight (composable leaving composition) and cancels the Ktor load job.
  2. Cancellation closes the underlying HTTP stream mid-read.
  3. That stream runs on the platform Android engine (java.net.HttpURLConnectioncom.android.okhttp), a frozen ~2015 okhttp fork whose AsyncTimeout throws Unbalanced enter/exit on a concurrent close.
  4. The exception is raised inside a coroutine cancellation handler on the main thread during Compose applyChanges, so nothing catches it and the app dies.

How we ended up on the platform engine

The platform engine was never chosen deliberately. The Jan 2026 OkHttp→Ktor migration (b93c73e3) dropped authlib's explicit pre-configured OkHttp engine in favour of a bare HttpClient { }, which resolves an engine via ServiceLoader from whatever is on the classpath. The only engine present was ktor-client-android (pulled in by auth-core-public), so every request, including Coil image loads sharing the base client, silently ran on the platform fork. Maintained com.squareup.okhttp3 handles the concurrent cancel cleanly and never throws on this path.

The fix

Select the engine explicitly per platform instead of relying on implicit ServiceLoader resolution.

Module Android JVM iOS
network-clients (base client: Coil + Apollo + all app traffic) OkHttp OkHttp Darwin
authlib (auth-token client) via jvm → OkHttp OkHttp Darwin
  • network-clients: commonMain gains expect fun httpClientEngineFactory(), used in buildKtorClient; actuals in androidMain/jvmMain (OkHttp, the maintained okhttp3 5.4.0 already shipping for Datadog) and nativeMain (Darwin).
  • authlib: same expect/actual pattern; removed the dangling HttpClientEngine import the migration left behind.
  • auth-core-public: dropped its ktor-client-okhttp dependency. It builds no client and only fed authlib's implicit engine off the classpath.
  • Version catalog: added ktor-client-okhttp, removed unused ktor-client-android and ktor-client-cio.

iOS behaviour is unchanged (still Darwin/NSURLSession); the crash was Android-only. A missing or wrong engine is now a compile-time error per platform rather than a silent runtime default, so this class of regression cannot recur unnoticed.

Verification

  • :app:assembleDebug
  • :network-clients:compileKotlinIosSimulatorArm64 + :authlib:compileKotlinIosSimulatorArm64
  • ktlint on both changed modules ✅

Fixes the Crashlytics crash "Exception in completion handler ... Cancelling"
whose root cause is "java.lang.IllegalStateException: Unbalanced enter/exit"
thrown from the platform's bundled com.android.okhttp AsyncTimeout.

Chain of events: Coil forgets an AsyncImagePainter while an image request is
in flight (composable leaving composition), cancels the Ktor load job, which
closes the underlying HTTP stream mid-read. That stream runs on the platform
Android engine (java.net.HttpURLConnection -> com.android.okhttp), a frozen
~2015 okhttp fork whose AsyncTimeout throws "Unbalanced enter/exit" on a
concurrent close. The exception is raised inside a coroutine cancellation
handler on the main thread during Compose applyChanges, so nothing catches it
and the app dies.

The platform engine was never chosen deliberately. The Jan 2026 OkHttp->Ktor
migration (b93c73e) dropped authlib's explicit pre-configured OkHttp engine in
favor of a bare HttpClient { }, which selects an engine via ServiceLoader from
whatever is on the classpath. The only engine present was ktor-client-android
(pulled in by auth-core-public), so every request, including Coil image loads
sharing the base client, silently ran on the platform fork.

Fix: select the engine explicitly per platform instead of relying on implicit
ServiceLoader resolution.

- network-clients (base client behind Coil, Apollo and all app traffic):
  androidMain and jvmMain use OkHttp (maintained okhttp3 5.4.0, already shipping
  for Datadog), nativeMain uses Darwin. iOS behavior is unchanged.
- authlib: same expect/actual engine selection (jvm OkHttp, ios Darwin); removed
  the dangling HttpClientEngine import left by the migration.
- auth-core-public: dropped its ktor-client-okhttp dependency; it builds no
  client and only fed authlib's implicit engine off the classpath.
- Version catalog: added ktor-client-okhttp, removed unused ktor-client-android
  and ktor-client-cio.

A missing or wrong engine is now a compile-time error per platform rather than a
silent runtime default, so this class of regression cannot recur unnoticed.

Verified: :app:assembleDebug plus iosSimulatorArm64 compilation of both modules
and ktlint all pass.
@StylianosGakis
StylianosGakis requested a review from a team as a code owner August 13, 2026 12:03
@panasetskaya
panasetskaya merged commit f9c02c1 into develop Aug 13, 2026
4 checks passed
@panasetskaya
panasetskaya deleted the fix/coil-image-loading-crash-okhttp-engine branch August 13, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants