RxJava2 Observable.zip(list) executes Network calls twice
Asked Answered
S

0

1

I'm getting an unwanted behavior/flaw when passing a list of Observable Network calls to Observable.Zip(), similar to this accepted answer:

And the unwanted behavior is, the network calls are being fired twice...

It's fired once when the Observable is added to the List, and then it's fired again during the Observable.zip()

Here's a boiled down snippet from my project that's reproducing the behavior:

    fun buildListOfObservableNetworkCalls(): Observable<Map<String, String?>> {

        val clients = mutableListOf<Observable<NetworkResponse>>()

        if (NetworkClient1.featureFlag) {
            val postBody = someMethodToBuildPostBody()
            clients.add(NetworkClient1().executeClient(postBody))
        }
        //There will be multiple NetworkClients in the near future
        return executeAllNetworkClients(headerBidClients)
    } 


     private fun executeAllNetworkClients(clients: List<Observable<NetworkResponse>>): Observable<Map<String, String?>> =

        if (clients.isNotEmpty()) {
            Observable.zip(clients) {
                it
            }
                .map { clientResults ->
                    clientResults.forEach { response ->
                        if (response is NetworkResponse) {
                            map[MY_KEY] += response.stringResult
                        }
                    }
                    map

                }.doOnSubscribe {
                    android.util.Log.d("LOGGER", "zip ON SUBSCRIBE")
                }
        } else {
            Observable.just(mapOf())
        }



    //**** My NetworkClient1 class containing the RxJava function that executes the network call ****//

    override fun executeClient(postBody: CustomPostBody): Observable<NetworkResponse> =
        retrofitApiInterface.networkCall1Request(postBody)
            .subscribeOn(Schedulers.io())
            .doOnSuccess { response ->
                Log.d("LOGGER", "Client1 ON SUCCESS")
            }
            .flatMapObservable { response ->
                Observable.just(
                    NetworkResponse(
                       response
                    )
                 )
            }.onErrorResumeNext { throwable: Throwable? ->
                android.util.Log.d("LOGGER", "Client1 ON ERROR")
                Observable.just(
                    NetworkResponse(
                       ""
                    )
                )
            }.doOnSubscribe {
                android.util.Log.d("LOGGER", "Client1 ON SUBSCRIBE")
            }
          


    //***** And my custom Interceptor which logs the double requests  ****////

class MyInterceptor: Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        Log.d("LOGGER", "intercept request at ${System.currentTimeMillis()}")
        val response = chain.proceed(request)
        Log.d("LOGGER", "intercept response at ${System.currentTimeMillis()}")
        return response
    }
}

And the log output is:

D/LOGGER: zip ON SUBSCRIBE
D/LOGGER: Client1 ON SUBSCRIBE 
D/LOGGER: intercept request at 1650059924358
D/LOGGER: intercept response at 1650059925747
D/LOGGER: Client1 ON SUCCESS
D/LOGGER: intercept request at 1650059925782
D/LOGGER: intercept response at 1650059925928

As you can see, the same network call is being executed twice.. and secondly, i'm also a bit puzzled as to why doOnSuccess isnt also called twice.

So, my main questions is, is there a way that I can build a list of Observables and pass it to Observable.zip() without executing the network call twice?

I see the issue is that I'm creating a List<Observable<NetworkResponse>> and in order to add network calls that return <Observable<NetworkResponse>>, I have to invoke the method as i'm adding them to the list. I know this may sound like a dumb question.. but is it at all possible to have a set-up where i'm able to add the Observable functions to the List without executing them? Probably over doing it, but would creating an extension function of .zip(iterable) which accepts a list of NetworkClients as the sources param and within the extension function, execute source.executeClient() be a feasible or stable solution?

I feel it would be inefficient if this was the unavoidable consequence of building a list of Observables to pass to zip(iterable), so i'm hoping that this is just a matter of my set-up rather than an overlooked consequence of the .zip(iterable) method.

I'm aware that I could avoid the above scenario by trying to pass each Observable Network call individually into the .zip() and use some sort of BiFunction to tie it all together. However, that doesn't seem very intuitive for my use case, being that I have to featureFlag check and build Post objects for each request that i'll be doing. Additionally, I'll be adding more NetWorkClients who's responses will all be returning the same base response type over the next few months, so I find the .zip(iterable) methodology as a clean and very scalable way of plugging in new NetworkClients.

Straub answered 16/4, 2022 at 0:2 Comment(3)
Where and how do you subscribe to this zip? Maybe you subscribe twice?Poulos
Hi @akarnokd, I just added more logs to show that subscribe happens once for the zip and once for NetworkClient1's network call.Straub
If you have an intercept, place a breakpoint at the log statement and see what's on the stacktrace when you hit the breakpoint the second time. You may also need to remove subscribeOn temporarily to not get confused by thread switches.Poulos

© 2022 - 2024 — McMap. All rights reserved.