Initialize Okhttp and Retrofit in background#7042
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors HTTP stack initialization so OkHttp/Retrofit (and dependent services) can be initialized asynchronously via SuspendProvider, aiming to reduce main-thread work during app startup and avoid ANRs caused by disk-backed TLS/keystore setup.
Changes:
- Introduces suspendable, mutex-guarded lazy initialization for
OkHttpClientandRetrofitinHomeAssistantApis, and rewires DI to expose these viaSuspendProvider. - Updates repositories/factories and various call sites (notifications, wear tiles, ExoPlayer data sources, wear settings) to consume suspend-provided OkHttp/Retrofit services.
- Adjusts Robolectric onboarding tests by seeding
Settings.Secure.ANDROID_ID, and changes Coil singleton initialization to useSingletonImageLoader.setSafe.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| wear/src/main/kotlin/io/homeassistant/companion/android/tiles/CameraTile.kt | Switches to suspend-provided OkHttp for fetching tile snapshot images |
| common/src/test/kotlin/io/homeassistant/companion/android/common/data/authentication/ServerRegistrationRepositoryTest.kt | Updates test construction to match SuspendProvider-based auth service |
| common/src/main/kotlin/io/homeassistant/companion/android/di/DataModule.kt | Replaces eager singletons with SuspendProvider bindings for HTTP/services and data source factory |
| common/src/main/kotlin/io/homeassistant/companion/android/common/util/UtilModule.kt | Injects SuspendProvider<DataSource.Factory> into AudioUrlPlayer |
| common/src/main/kotlin/io/homeassistant/companion/android/common/util/ExoPlayerExt.kt | Updates mTLS-aware data source factory to take a concrete OkHttp client |
| common/src/main/kotlin/io/homeassistant/companion/android/common/util/AudioUrlPlayer.kt | Defers DataSource.Factory acquisition via SuspendProvider |
| common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/WebSocketRepository.kt | Makes repository factory creation suspendable |
| common/src/main/kotlin/io/homeassistant/companion/android/common/data/websocket/WebSocketCore.kt | Makes core factory creation suspendable and uses suspend-provided OkHttp |
| common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/IntegrationRepository.kt | Replaces assisted factory with injected, suspendable factory pattern |
| common/src/main/kotlin/io/homeassistant/companion/android/common/data/HomeAssistantApis.kt | Adds async, mutex-guarded lazy initialization for OkHttp/Retrofit |
| common/src/main/kotlin/io/homeassistant/companion/android/common/data/authentication/ServerRegistrationRepository.kt | Switches auth calls to use suspend-provided AuthenticationService/installId |
| common/src/main/kotlin/io/homeassistant/companion/android/common/data/authentication/impl/AuthenticationRepositoryImpl.kt | Removes assisted injection, aligns construction with new factory approach |
| common/src/main/kotlin/io/homeassistant/companion/android/common/data/authentication/AuthenticationRepository.kt | Updates factory to inject suspend-provided service and installId |
| app/src/test/kotlin/io/homeassistant/companion/android/settings/wear/SettingsWearRepositoryTest.kt | Updates test wiring for provider-based services |
| app/src/test/kotlin/io/homeassistant/companion/android/onboarding/WearOnboardingNavigationTest.kt | Seeds ANDROID_ID in Robolectric setup to avoid DI crash |
| app/src/test/kotlin/io/homeassistant/companion/android/onboarding/BaseOnboardingNavigationTest.kt | Seeds ANDROID_ID in shared Robolectric setup to avoid DI crash |
| app/src/main/kotlin/io/homeassistant/companion/android/widgets/camera/CameraWidget.kt | Removes direct OkHttp injection (presumably moved behind other abstractions) |
| app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt | Uses suspend-provided DataSource.Factory for ExoPlayer initialization |
| app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt | Uses suspend-provided OkHttp for downloading notification media |
| app/src/main/kotlin/io/homeassistant/companion/android/HomeAssistantApplication.kt | Initializes Coil singleton ImageLoader asynchronously using suspend-provided OkHttp |
| app/src/main/kotlin/io/homeassistant/companion/android/frontend/exoplayer/FrontendExoPlayerManager.kt | Uses suspend-provided DataSource.Factory to build ExoPlayer |
| app/src/full/kotlin/io/homeassistant/companion/android/settings/wear/SettingsWearRepository.kt | Migrates wear repository calls to provider-based Retrofit services |
| app/src/debug/kotlin/io/homeassistant/companion/android/developer/DemoExoPlayerActivity.kt | Defers data source factory creation in Compose using produceState |
Comments suppressed due to low confidence (1)
app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt:1514
- The video download path reads from
response.body.source()without null-checkingbodyand without closing theResponse, which can crash (null body) and leak sockets/file descriptors.
val response = okHttpClientProvider().newCall(request).execute()
if (!videoFile.exists()) {
videoFile.parentFile?.mkdirs()
videoFile.createNewFile()
| @SuppressLint("HardwareIds") | ||
| @Provides | ||
| @NamedDeviceId | ||
| @Singleton | ||
| fun provideDeviceId(@ApplicationContext appContext: Context) = Settings.Secure.getString( | ||
| fun provideDeviceId(@ApplicationContext appContext: Context): String = Settings.Secure.getString( | ||
| appContext.contentResolver, | ||
| Settings.Secure.ANDROID_ID, | ||
| ) |
There was a problem hiding this comment.
I figure it out while working on this.
There was a problem hiding this comment.
I don't know what you mean here but the suggestion is correct IMO (type can be inferred)
There was a problem hiding this comment.
I had a IDE warning, also in our codebase we assume it's non null.
There was a problem hiding this comment.
What I've figured out is that within test this is not set so it has to be set manually otherwise the DI crashes.
df38382 to
96808b3
Compare
2befcd5 to
8ed0a77
Compare
8ed0a77 to
a1949e5
Compare
|
Possibly related: #6119. |
Summary
In #7038 we hit the fact that we are currently loading okhttp on the main thread, because we might load a keystore from the disk which is wrong. Instantiating OkHttp and Retrofit can be expensive and our Application is experiencing some ANR at application start already.

This PR move the initialization out of the main thread by using the
SuspendProviderit has a lot of implications because we need OkHttp/Retrofit in many places and the usage of it also cascade into the DI graph so we need to adjust the factories to work with the fact that it requires a coroutineScope now.One issue popped from the robolectric test of the onboarding because it was trying to provide the deviceID that is not available. I've made a quick fix but I'm pretty sure that we will revisit it later when more issues comes in.
I had to use another method to initialize Coil in an async manner
SingletonImageLoader.setSafeChecklist
Any other notes
I would like 1 week of beta of this, since it might have some impacts.