0

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
                )
            }
        }
    }
}
  1. What’s the recommended pattern to make this non-blocking but safe?
  2. Is there a Hilt-friendly way to provide an async dependency without blocking app start but still letting dependents access a current value?
  3. Any best practices to avoid blocking the main thread while still ensuring required initialization on app launch?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.