Skip to content

Interceptor occasionally not returning callbacks of RxJava #3549

Open

Description

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)

  • Interceptor : A/INTERCEPT: FALLBACK TO CACHE Unable to resolve host "some.host.com": No address associated with hostname

  • onError (error ->) is called if no cache

  • onSuccess (result ->) is called if cache is available

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)

  • onError (error ->) is called when response was failed such as 404, etc.

  • onSuccess (result ->) is called when response was success

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)

  • Interceptor : A/INTERCEPT: FALLBACK TO CACHE Unable to resolve host "some.host.com": No address associated with hostname

  • onError (error ->) is not called

  • onSuccess (result ->) is not called

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'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions