apollo-kotlin
apollo-kotlin copied to clipboard
Subscription Error: Cannot convert undefined or null to object
Version
3.8.2
Summary
I am setting up a new subscription using a localhost GraphQL server. When calling ApolloClient.subscription, this error occurs: Cannot convert undefined or null to object. The ApolloClient works for queries and mutations, but not subscriptions. This subscription works in GraphiQL.
Steps to reproduce the behavior
Here is the ApolloClient setup:
val ApolloClient = ApolloClient.Builder()
.serverUrl("http://10.0.2.2:8000/graphql")
.webSocketServerUrl("ws://10.0.2.2:8000/graphql")
.webSocketReopenWhen { throwable, attempt ->
Log.d("ApolloClient", "WebSocket got disconnected, reopening after a delay", throwable)
delay(attempt * 1000)
true
}
.okHttpClient(
OkHttpClient.Builder()
.addInterceptor(AuthorizationInterceptor())
.build()
)
.build()
Here is the subscription:
subscription RoleSampleSubscription {
listen(topic: "RoleSample") {
query {
allRoles {
nodes {
id
name
}
}
}
}
}
Here is the call to the subscription, which triggers the error:
val roleSubscription = ApolloClient.subscription(RoleSampleSubscription()).toFlow()
When the same subscription is initiated in GraphiQL, it initially shows, "Waiting for subscription to yield data…". Then, when this call is made in the PostgreSQL database, "SELECT pg_notify('postgraphile:RoleSample', '');", the rows are returned.
Note the GraphQL server is setup using Postgraphile.
Logs
WebSocket got disconnected, reopening after a delay
com.apollographql.apollo3.exception.ApolloNetworkException: Connection error:
{type=connection_error, payload={message=Cannot convert undefined or null to object}}
at com.apollographql.apollo3.network.ws.SubscriptionWsProtocol$connectionInit$2.invokeSuspend(SubscriptionWsProtocol.kt:42)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
at java.lang.Thread.run(Thread.java:1012)
Hi 👋
This error ({message=Cannot convert undefined or null to object}
) is returned by your server in response to the connection_init
message.
There are different possible websockets protocols for subscriptions and I'm suspecting the server you are talking to expects a different protocol than the Apollo Kotlin default. Can you try using graphql-ws
?
val apolloClient = ApolloClient.Builder()
.subscriptionNetworkTransport(
WebSocketNetworkTransport.Builder()
.protocol(GraphQLWsProtocol.Factory())
.serverUrl("ws://10.0.2.2:8000/graphql")
.build()
)
.build()
If that doesn't work, you have more options in that page of the documentation.
Something else worth trying is double check you url. Sometimes servers use a different path for subscriptions vs queries/mutation.
Hope this helps. Let me know how that goes!
Hi @chris-guidry any news here? Where you able to fin a solution?
@martinbonnin I apologize I have not had a chance to get back to testing those suggestions, but I really appreciate your input and I will try changing the protocol parameter soon!
@chris-guidry I'll close this one for now. Please let us know if you need anything from us and I'll reopen.
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Kotlin usage and allow us to serve you better.
Hi @martinbonnin, I finally had a chance to test a few changes, but the issue is still not resolved :-(
To verify the server's URL and protocol, I reviewed the settings in GraphiQL since subscriptions are working there. In Chrome the websocket Request URL matches the settings I am using so I don't think that's the problem. Under Sec-Websocket-Protocol, it shows "graphql-transport-ws". Is that the same as graphql-ws?
On the Kotlin client side, I tried adding the subscriptionNetworkTransport parameter to the ApolloClient configuration as suggested, but then when the subscription is initiated, this error occurs: IllegalStateException: Apollo: 'webSocketEngine' has no effect if 'subscriptionNetworkTransport' is set
. I have not found any references in my code to "webSocketEngine" so I'm not sure how to resolve that error.
Also, I believe my GraphQL server supports both subscriptions-transport-ws and graphql-ws protocols based on this post: https://github.com/graphile/crystal/issues/1525
it shows "graphql-transport-ws". Is that the same as graphql-ws?
This is super confusing but most likely yes, see https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#communication
IllegalStateException: Apollo: 'webSocketEngine' has no effect if 'subscriptionNetworkTransport' is set. I have not found any references in my code to "webSocketEngine" so I'm not sure how to resolve that error.
This is unexpected. Can you try from a plain builder?
val apolloClient = ApolloClient.Builder()
.serverUrl("http://localhost:9090/graphql")
.subscriptionNetworkTransport(
WebSocketNetworkTransport.Builder().serverUrl(
serverUrl = "http://localhost:9090/graphql",
).protocol(
protocolFactory = GraphQLWsProtocol.Factory()
).build()
)
.build()
Well, it's mostly working now. I thought the websocket URL needed to use the "ws://" protocol, but it's working with "http://".
The lingering issue is that enabling the parameters webSocketReopenWhen and okHttpClient seem to be the cause of the error, 'webSocketEngine' has no effect if 'subscriptionNetworkTransport' is set
. Do those two parameters need to be applied differently in this case?
Here is what it looks like right now:
val ApolloClient = ApolloClient.Builder()
.serverUrl(BuildConfig.GRAPHQL_URL)
.subscriptionNetworkTransport(
WebSocketNetworkTransport
.Builder()
.serverUrl(BuildConfig.GRAPHQL_URL)
.protocol(GraphQLWsProtocol.Factory())
.build()
)
// .webSocketReopenWhen { throwable, attempt ->
// Log.d("ApolloClient", "WebSocket got disconnected, reopening after a delay", throwable)
// delay(attempt * 1000)
// true
// }
// .okHttpClient(
// OkHttpClient.Builder()
// .addInterceptor(AuthorizationInterceptor())
// .build()
// )
.build()
You can set both of those parameters on WebSocketNetworkTransport.Builder
directly:
val ApolloClient = ApolloClient.Builder()
.serverUrl(BuildConfig.GRAPHQL_URL)
.subscriptionNetworkTransport(
WebSocketNetworkTransport
.Builder()
.okHttpClient(okHttpClient)
.reopenWhen { throwable, attempt -> }
.serverUrl(BuildConfig.GRAPHQL_URL)
.protocol(GraphQLWsProtocol.Factory())
.build()
)
ApolloClient.Builder.okHttpClient(OkHttpClient)
is a shortcut that configures the engine but that doesn't work if you use the "full" WebSocketNetworkTransport
API
@martinbonnin thank you so much for your help! The subscription is working well now.
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Kotlin usage and allow us to serve you better.
@martinbonnin I don't need this issue re-opened, but I wanted to mention for anyone reading this later that adding the okHttpClient as part of the WebSocketNetworkTransport builder compiled, but didn't work when using addInterceptor with the OkHttpClient builder to append a JWT to server requests (queries and mutations).
Instead, I added it to the ApolloClient builder this way: .httpEngine(DefaultHttpEngine(okHttpClient))
.