apollographql / apollo-kotlin

:rocket: Β A strongly-typed, caching GraphQL client for the JVM, Android, and Kotlin multiplatform.
https://www.apollographql.com/docs/kotlin
MIT License
3.73k stars 655 forks source link

Apollo 3.0.0-beta2 subscriptions do not collects #3583

Closed simondorociak closed 2 years ago

simondorociak commented 2 years ago

I have this in Fragment and subscription do not collect:

lifecycleScope.launch {
            client.subscribe(<my_subscription>).execute()
                .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
                .retryWhen { cause, _ ->
                    cause.printStackTrace()
                    delay(10000L)
                    true
                }
                .collect {
                    Timber.d("onNewMessage invoked")
                }
        }

Timber.d("onNewMessage invoked") is never invoked. Can you tell why this happen? Thanks.

martinbonnin commented 2 years ago

Hi πŸ‘‹ Can you share your ApolloClient construction code? It might be that subscriptions require a different url?

val apolloClient = ApolloClient.Builder()
  .serverUrl("https://example.com/graphql")
  .webSocketServerUrl("wss://example.com/subscriptions")
  .build()

See here for more details.

simondorociak commented 2 years ago

I will send you asap thanks!

S pozdravom / Best regards

Ing. Ε imon Dorociak Android Engineer Tel: +420 776 226 886


From: Martin Bonnin @.> Sent: Saturday, November 20, 2021 4:19:02 PM To: apollographql/apollo-android @.> Cc: Simon Dorociak @.>; Author @.> Subject: Re: [apollographql/apollo-android] Apollo 3.0.0-beta2 subscriptions do not collects (Issue #3583)

Hi πŸ‘‹ Can you share your ApolloClient construction code? It might be that subscriptions require a different url?

val apolloClient = ApolloClient.Builder()

.serverUrl("https://example.com/graphql")

.webSocketServerUrl("wss://example.com/subscriptions")

.build()

See herehttps://www.apollographql.com/docs/android/v3/essentials/subscriptions/ for more details.

β€” You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/apollographql/apollo-android/issues/3583#issuecomment-974664261, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ABXX3PDSMTQR2F4KZZCUYFTUM636NANCNFSM5IMY5LIA. Triage notifications on the go with GitHub Mobile for iOShttps://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Androidhttps://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

simondorociak commented 2 years ago

Hi πŸ‘‹ Can you share your ApolloClient construction code? It might be that subscriptions require a different url?

val apolloClient = ApolloClient.Builder()
  .serverUrl("https://example.com/graphql")
  .webSocketServerUrl("wss://example.com/subscriptions")
  .build()

See here for more details.

Im configuring ApolloClient like this:

@Provides
    @Singleton
    fun provideApolloClient(
            application: Application,
            prefs: SharedPrefsManager,
            httpClient: OkHttpClient) : ApolloClient {
        return ApolloClient.Builder()
            .networkTransport(
                HttpNetworkTransport(
                    serverUrl = BuildConfig.SERVER_URL,
                    okHttpClient = httpClient
                )
            )
            .subscriptionNetworkTransport(
                AuthSocketSubscriptionTransport(
                    prefs,
                    BuildConfig.SOCKET_URL,
                    DefaultWebSocketEngine(httpClient)
                )
            )
            .addInterceptor(RefreshTokenInterceptor(application, prefs))
            .build()
    }

I dont see function webSocketServerUrl in my project.

In my case I have my own AuthSocketSubscriptionTransport and also WsProtocol because I have to pass JWT token in payload but this I just copy & paste your default subs. transport and I just modified piece of code where Im puting JWT token into payload:

class AuthSubscriptionWsProtocol(
    prefs: SharedPrefsManager,
    webSocketConnection: WebSocketConnection,
    listener: Listener,
    private val connectionAcknowledgeTimeoutMs: Long = 10_000,
    private val connectionPayload: suspend () -> Map<String, Any?>? = { mapOf("Authorization" to  "JWT ${prefs.getJwtToken()}") },
) : WsProtocol(webSocketConnection, listener)

Our socket url is classic : wss:///our.domain/graphql/.

It worked on version 2 but version 2 had some bugs which were for us important so we switched to version 3 because we want to use new technologies as possible.

I checked code, added some logging and I see in logcat that is called this function in our AuthSocketSubscriptionTransport:

override fun <D : Operation.Data> execute(
        request: ApolloRequest<D>,
    ): {  return events.onSubscription { ... } }

This normally pass through filter and transformWhile functions and also with map but in Fragment i dont receive event from socket in collect callback.

Also our backend developer reported that sometimes its problem that he see in log errors that subscription was successful after two unsuccessful attempts which didnt happen when we used version 2 and backend code wasnt changed.

simondorociak commented 2 years ago

@martinbonnin I fixed issue! It was caused by my incorrect implementation of ApolloInterceptor. I will write my usercase maybe it will help someone else to fix issue.

Scenario: Apollo doesn't have component called "Authenticator" which is used by okhttp it works for REST but not for GraphQL. So. in order to achieve this behaviour I implemented custom ApolloInterceptor in which Im executing request by:

chain.proceed(request).single()

and then checking for errors and exact 401 HTTP code. This works well but that single() function someone changes also request of subscription and this caused that I did not receive collect callbacks.

So I added condition that if I'm intercepting subscription call i will just pass it with proceed and intercept all other http requests (operations).

val operationName = request.operation.name()
if (operationName.contains(OPERATION_SUBSCRIPTION, true)) {
   Timber.d("Subscription request detected -> do not intercept")
      return chain.proceed(request)
} else {
   // intercept other requests
}

Thank you for your time.

martinbonnin commented 2 years ago

Thanks for the follow up πŸ™