I have a CacheCreation class that prepares config for an OkHttp interceptor. It’s created during DI on app launch, and we currently use runBlocking so the object graph is ready before the first network call. This blocks startup on the main thread.
Even if we move work to a background dispatcher, the main thread still waits since dependents need the result synchronously to build the client/interceptor chain.
Is the usual practice to inject defaults at DI time and update in-place later?
But since this is required to create okhttp client we cannot update later we need to create new client again, with dynamic interceptor you cannot change things like the cache directory or cache size after the OkHttpClient is built.
// Module provides an OkHttpClient with cache interceptor
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
@BaseClientWithOnlyCacheInterceptor
fun provideOkHttpClientWithCacheInterceptor(
@BaseClientWithNoInterceptor okHttpClient: OkHttpClient,
cacheHeaderInterceptor: CacheHeaderInterceptor,
cacheConfigProvider: AppCacheConfigProvider
): OkHttpClient {
val builder = okHttpClient.newBuilder()
.cacheConfig(cacheConfigProvider.getCacheConfig())
if (cacheConfigProvider.getCacheConfig().enableCustomCacheInterceptor) {
builder.addNetworkInterceptor(cacheHeaderInterceptor)
}
return builder.build()
}
}
// Implementation with runBlocking in init
@Singleton
class AppCacheConfigProviderImpl @Inject constructor(
private val config: Config,
@ApplicationContext context: Context,
) : AppCacheConfigProvider {
private var sessionCache: Cache?
private var crossSessionCache: Cache?
private var cacheControlConfig: CacheControlConfig
private var appCacheConfig: AppCacheConfig
private var maxAgeUpperBound: Int
private val cacheLock = Any()
init {
// PROBLEM: blocks startup on main thread
runBlocking {
val cacheConfig = preloadConfig.getCacheControlConfig()
crossSessionCache = if (cacheConfig.diskCacheMaxSizeMb > 0) {
Cache(
File(context.cacheDir, cacheConfig.diskCacheFolderName),
(cacheConfig.diskCacheMaxSizeMb * 1024 * 1024).toLong(),
)
} else null
val cacheSize =
(cacheConfig.inMemCacheMaxSizeMb.coerceAtMost(
context.getAvailableMemory() * cacheConfig.inMemCachePercent / 100
)) * 1024 * 1024
sessionCache = if (cacheSize > 0) {
Cache(File(context.cacheDir, "session"), cacheSize.toLong())
} else null
CoroutineScope(Dispatchers.IO).launch {
synchronized(cacheLock) {
try {
sessionCache?.evictAll()
} catch (e: Exception) {}
}
}
cacheControlConfig = config.getCacheControlConfig()
maxAgeUpperBound = config.getMaxAgeUpperBound()
synchronized(cacheLock) {
appCacheConfig = AppCacheConfig(
crossSessionCache,
sessionCache
)
}
}
}
}
- What’s the recommended pattern to make this non-blocking but safe?
- Is there a Hilt-friendly way to provide an async dependency without blocking app start but still letting dependents access a current value?
- Any best practices to avoid blocking the main thread while still ensuring required initialization on app launch?