square / retrofit

A type-safe HTTP client for Android and the JVM
https://square.github.io/retrofit/
Apache License 2.0
43.14k stars 7.3k forks source link

Interceptor occasionally not returning callbacks of RxJava #3549

Open ArcherEmiya05 opened 3 years ago

ArcherEmiya05 commented 3 years ago

I am having trouble with Interceptor as it does not call onError nor onSuccess on specific scenario

This is my interface class

interface EndpointServices {

    companion object {

private fun interceptor(): Interceptor {
            return Interceptor { chain ->
                val request: Request = chain.request()
                val originalResponse: Response = chain.proceed(request)

                originalResponse.newBuilder()
                    .build()

            }
        }

        private fun onlineOfflineHandling(): Interceptor {
            return Interceptor { chain ->
                try {
                    Log.wtf("INTERCEPT", "FETCH ONLINE")
                    val cacheControl = CacheControl.Builder()
                        .maxAge(5, TimeUnit.SECONDS)
                        .build()

                    val response = chain.proceed(chain.request().newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", "public, $cacheControl")
                        .build())

                    Log.wtf("INTERCEPT", "CACHE ${response.cacheResponse} NETWORK ${response.networkResponse}")

                    response
                } catch (e: IOException) {
                    Log.wtf("INTERCEPT", "FALLBACK TO CACHE ${e.message}")

                    val cacheControl: CacheControl = CacheControl.Builder()
                        .maxStale(30, TimeUnit.DAYS)
                        .onlyIfCached() // Use Cache if available
                        .build()

                    val offlineRequest: Request = chain.request().newBuilder()
                        .cacheControl(cacheControl)
                        .build()

                    val response = chain.proceed(offlineRequest)

                    Log.wtf("INTERCEPT", "CACHE ${response.cacheResponse} NETWORK ${response.networkResponse}")

                    response
                }
            }
        }

        fun create(baseUrl: String, cacheDir: File): EndpointServices {

            // Inexact 150 MB of maximum cache size for a total of 4000 assets where 1MB/30 assets
            // The remaining available space will be use for other cacheable requests
            val cacheSize: Long = 150 * 1024 * 1024

            val cache = Cache(cacheDir, cacheSize)

            Log.wtf("CACHE DIRECTORY", cache.directory.absolutePath)

            for (cacheUrl in cache.urls())
                Log.wtf("CACHE URLS", cacheUrl)

            Log.wtf("CACHE OCCUPIED/TOTAL SIZE", "${cache.size()}/${cache.maxSize()}")

            /*val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY*/

            val httpClient = OkHttpClient.Builder()
                .cache(cache)
                /*.addInterceptor(interceptor)*/
                .callTimeout(10, TimeUnit.SECONDS)
                .connectTimeout(10, TimeUnit.SECONDS)
                .addNetworkInterceptor(interceptor())
                .addInterceptor(onlineOfflineHandling())
                .build()

            val retrofit = Retrofit.Builder()
                .addCallAdapterFactory(
                    RxJava2CallAdapterFactory.create()
                )
                .addConverterFactory(
                    MoshiConverterFactory.create()
                )
                .client(httpClient)
                .baseUrl(baseUrl)
                .build()

            return retrofit.create(EndpointServices::class.java)

        }

}

@GET("{fullPath}")
    fun getExchangeItems(
        @Path("fullPath", encoded = true) fullPath: String,
        @Query("fields") fields: String
    ):
            Single<ExchangeItemModel>

}

Fetching it with this

private val RetroService by lazy {
        EndpointServices.create(MySingleton.assetLink, application.cacheDir)
    }

RetroService.getExchangeItems(
            MySingleton.exchange.replace("{ID}", assetName.trim().replace(" ", "-")),
            MySingleton.exchangeField
        )
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { result ->

                    // Filter the item to display
                    includedAsset.value =
                        result.data.filter { it.exchangeName != null && !it.excludedFromPrice}

                    excludedAsset.value =
                        result.data.filter { it.exchangeName != null && it.excludedFromPrice}

                },
                { error ->
                    Log.wtf("WTF", "${error.message}")
                    FirebaseCrashlytics.getInstance().recordException(error)
                    // Only change the UI with this conditions
                    if ((includedAsset.value!!.isEmpty() && excludedAsset.value!!.isEmpty()) || (error is HttpException && error.code() == HttpURLConnection.HTTP_GATEWAY_TIMEOUT))
                        errorMessage.value = R.string.swipe_to_refresh
                }
            ))

Case 1: No network (Wifi/Mobile Data is OFF)

All Good! We show error UI to user during offline mode if no cache exist else we show the list if cache is at least available.

========================================================================

Case 2: Connected to network (Wifi/Mobile Data is ON and the network has INTERNET SERVICE)

All Good! BUT due to unknown circumstances sometimes I got A/INTERCEPT: FALLBACK TO CACHE CANCELED from the Interceptor and when this happen I don't receive any callbacks neither onError nor onSuccess thus the UI for loading never ends.

========================================================================

Case 3: Connected to network (Wifi/Mobile Data is ON but the network has NO INTERNET SERVICE)

As you can see, my Interceptor log here is just the same in our first case yet no callback has been return even a cache is available thus the UI for loading never ends again.

dependencies

implementation 'com.squareup.moshi:moshi-kotlin:1.11.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'

    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'

    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    // Because RxAndroid releases are few and far between, it is recommended you also
    // explicitly depend on RxJava's latest version for bug fixes and new features.
    // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version)
    implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
ArcherEmiya05 commented 3 years ago

Any update?

falgunirana2022 commented 1 year ago

Hii @ArcherEmiya05 , Did you find any solution to this issue?

shriharsha-bhagwat commented 1 year ago

@ArcherEmiya05 @falgunirana2022 Do we have any updates for this issue?