https://kotlinlang.org logo
Title
d

Doug Chappelle

05/03/2021, 2:21 PM
@mbonnin does the tutorial using 3.0.0-dev2 https://github.com/apollographql/apollo-android-tutorial/tree/3.0.0. use websockets for mutations and queries?
m

mbonnin

05/03/2021, 2:21 PM
Hi Doug! Nice to see you here 👋 🙂
Unfortunately not, we're missing a server to test this against
The vast majority of the servers out there use plain HTTP queries and mutations
d

Doug Chappelle

05/03/2021, 2:24 PM
I guess you don't have access to the servers used for the iOS and nodejs implementations :(
m

mbonnin

05/03/2021, 2:25 PM
Maybe? Do you have a url somewhere?
d

Doug Chappelle

05/03/2021, 2:27 PM
No, I don't know what they used.
m

mbonnin

05/03/2021, 2:29 PM
Yea, it's not easy to find those
d

Doug Chappelle

05/03/2021, 3:00 PM
For some reason I don't see the same Apollo client interface testing with com.apollographql.apollo3:apollo-coroutines-support:3.0.0-dev8 and com.apollographql.apollo3:apollo-runtime-kotlin:3.0.0-dev8 as I do when I cloned the repo and checked out the tag v3.0.0-dev8?
m

mbonnin

05/03/2021, 3:01 PM
You can drop
apollo-coroutines-support
, Apollo 3 exposes a API interface by default
Apollo 3 is coroutines first so it has no
toFlow
to
await
d

Doug Chappelle

05/03/2021, 3:23 PM
I still don't understand why the code looks different above 🤔. However, removing
apollo-coroutines-support
fixed my duplicate definition issue 😀
Old code to successfully subscribe but query & mutation were POSTs..
val okHttpClient = OkHttpClient.Builder()
        .addNetworkInterceptor(LoggingInterceptor())
        .hostnameVerifier(NullHostNameVerifier())
        .build()

wsClient = ApolloClient.builder()
        .serverUrl("https://$host/websocket/realtime/websocket")
        .subscriptionTransportFactory(WebSocketSubscriptionTransport.Factory("wss://$host/websocket/realtime/websocket", okHttpClient))
        .subscriptionConnectionParams(SubscriptionConnectionParams(mapOf("x-vcs-token" to securityToken)))
        .okHttpClient(okHttpClient)
        .build()
New code where the subscription isn't even successful (timeout) 🤔
val wsFactory = ApolloWebSocketFactory("https://$host/websocket/realtime/websocket",
    mapOf("Sec-WebSocket-Protocol" to "graphql-transport-ws"))

val transport = ApolloWebSocketNetworkTransport(wsFactory, mapOf("x-vcs-token" to securityToken))

wsClient = ApolloClient.Builder()
    .subscriptionNetworkTransport(transport)
    .networkTransport(transport)
    .build()
m

mbonnin

05/03/2021, 6:22 PM
Any chance you can create a small codesandbox that would use queries over websocket?
But I'm not too familiar with
graphql-ws
and other things
d

Doug Chappelle

05/03/2021, 6:46 PM
Helix looks like it doesn't use websockets. I'll have to try and see if we can't expose a very simple interface for testing. In the meantime are there apollo logs I can turn on / tools I could use? I would love to see the subscription message being sent
👍 1
m

mbonnin

05/03/2021, 6:47 PM
Your best bet is breakpoints. They usually work well
Otherwise, you could make a local build (
./gradlew publishToMavenLocal
) and point your project to
mavenLocal()
d

Doug Chappelle

05/03/2021, 9:42 PM
Our backend uses https://www.npmjs.com/package/graphql-ws it has a getting started server setup that might be useful for test purposes.
With the following code the websocket is being opened to the server but the server never receives the connection_init on time and closes the websocket with 4408: Connection initialisation timeout 🤔
val wsFactory = ApolloWebSocketFactory("https://$host/websocket/realtime/websocket")

val transport = ApolloWebSocketNetworkTransport(wsFactory, mapOf("x-vcs-token" to securityToken))

wsClient = ApolloClient.Builder()
    .subscriptionNetworkTransport(transport)
    .networkTransport(transport)
    .build()
The problem is the connection_init is sent but as binary 🤨
m

mbonnin

05/05/2021, 9:43 AM
Alright, finally got what I think is a test server up and running thanks to some help: https://codesandbox.io/s/6hwhx?file=/src/index.js
@Doug Chappelle any chance you can try against this? See if it reproduces your issue?
d

Doug Chappelle

05/05/2021, 12:49 PM
@mbonnin I'll take a look. I've gotten further by sending text instead of bytes in the websocket. The channel's still use ByteString but I'm not sure that matters. I'm to the point that can successfully subscribe but query and mutate are crashing on a casting issue e.g.,
Failed to retrieve TURN info: java.lang.ClassCastException: com.unify.vcs.realtime-svc-api.TurnInfoByIdQuery cannot be cast to com.apollographql.apollo3.api.Subscription
m

mbonnin

05/05/2021, 1:17 PM
Is there more to this stacktrace? Looks like there's some place where the client harcodes subscriptions...
d

Doug Chappelle

05/05/2021, 2:18 PM
backtrace = {Object[41]@18974} 0 = {int[80]@18982} 1 = {Class@18265} "class com.apollographql.apollo3.network.ws.ApolloWebSocketNetworkTransport$execute$$inlined$flatMapLatest$1$lambda$4" 2 = {Class@18265} "class com.apollographql.apollo3.network.ws.ApolloWebSocketNetworkTransport$execute$$inlined$flatMapLatest$1$lambda$4" 3 = {Class@18355} "class kotlinx.coroutines.flow.FlowKt__EmittersKt$onStart$$inlined$unsafeFlow$1" 4 = {Class@18353} "class kotlinx.coroutines.flow.FlowKt__EmittersKt$onCompletion$$inlined$unsafeFlow$1" 5 = {Class@18025} "class com.apollographql.apollo3.network.ws.ApolloWebSocketNetworkTransport$execute$$inlined$flatMapLatest$1" 6 = {Class@18025} "class com.apollographql.apollo3.network.ws.ApolloWebSocketNetworkTransport$execute$$inlined$flatMapLatest$1" 7 = {Class@18235} "class kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3$invokeSuspend$$inlined$collect$1$lambda$1" 8 = {Class@18235} "class kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3$invokeSuspend$$inlined$collect$1$lambda$1" 9 = {Class@17320} "class kotlinx.coroutines.intrinsics.UndispatchedKt" 10 = {Class@16834} "class kotlinx.coroutines.CoroutineStart" 11 = {Class@17317} "class kotlinx.coroutines.AbstractCoroutine" 12 = {Class@17450} "class kotlinx.coroutines.BuildersKt__Builders_commonKt" 13 = {Class@16832} "class kotlinx.coroutines.BuildersKt" 14 = {Class@17450} "class kotlinx.coroutines.BuildersKt__Builders_commonKt" 15 = {Class@16832} "class kotlinx.coroutines.BuildersKt" 16 = {Class@18229} "class kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3$invokeSuspend$$inlined$collect$1" 17 = {Class@18261} "class kotlinx.coroutines.flow.FlowKt__TransformKt$filterNotNull$$inlined$unsafeTransform$1$2" 18 = {Class@18247} "class kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1" 19 = {Class@18247} "class kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1" 20 = {Class@18126} "class kotlinx.coroutines.flow.internal.SafeCollector" 21 = {Class@18126} "class kotlinx.coroutines.flow.internal.SafeCollector" 22 = {Class@18012} "class com.apollographql.apollo3.network.ws.ApolloWebSocketNetworkTransport$getServerConnection$1" 23 = {Class@18012} "class com.apollographql.apollo3.network.ws.ApolloWebSocketNetworkTransport$getServerConnection$1" 24 = {Class@18121} "class kotlinx.coroutines.flow.SafeFlow" 25 = {Class@18120} "class kotlinx.coroutines.flow.AbstractFlow" 26 = {Class@18254} "class kotlinx.coroutines.flow.FlowKt__TransformKt$filterNotNull$$inlined$unsafeTransform$1" 27 = {Class@18145} "class kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3" 28 = {Class@18145} "class kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3" 29 = {Class@17320} "class kotlinx.coroutines.intrinsics.UndispatchedKt" 30 = {Class@18146} "class kotlinx.coroutines.flow.internal.FlowCoroutineKt" 31 = {Class@18135} "class kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest" 32 = {Class@18134} "class kotlinx.coroutines.flow.internal.ChannelFlowOperator" 33 = {Class@18134} "class kotlinx.coroutines.flow.internal.ChannelFlowOperator" 34 = {Class@18139} "class kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1" 35 = {Class@16827} "class kotlin.coroutines.jvm.internal.BaseContinuationImpl" 36 = {Class@16732} "class kotlinx.coroutines.DispatchedTask" 37 = {Class@17392} "class kotlinx.coroutines.scheduling.CoroutineScheduler" 38 = {Class@17407} "class kotlinx.coroutines.scheduling.CoroutineScheduler$Worker" 39 = {Class@17407} "class kotlinx.coroutines.scheduling.CoroutineScheduler$Worker" 40 = {Class@17407} "class kotlinx.coroutines.scheduling.CoroutineScheduler$Worker"
I can look into it, give me a bit of time as I'm wrapping up something, will look into it this week
🙌 1
d

Doug Chappelle

05/05/2021, 5:27 PM
I hacked the code to use subscription: Operation<*> instead. I can now send query & mutations over the websocket but I'm working on a new issue now... not sure if my hacks to apollo are involved
m

mbonnin

05/05/2021, 11:19 PM
Been digging a bit more but I can't get the
graphql-ws
samples to work. I managed to get to the point where both client and server start but don't talk to each other
Websockets should work over localhost, right?
Alright, it was because of
wss
instead of
ws
🤦‍♂️
d

Doug Chappelle

05/06/2021, 4:41 PM
I have an updated/hacked version with graphql-ws working and queries & subscribes using the websocket. For the websocket part I only had to make the change I mentioned above to fix the cast and send data as a string and not bytes 🙂. I can share the changes with you if you like.
m

mbonnin

05/06/2021, 5:06 PM
Oh yes please 🙏 That'd be awesome
d

Doug Chappelle

05/06/2021, 9:24 PM
https://github.com/DNogginator-Inc/apollo-android/tree/graphql-ws Branched off of v3.0.0-dev8. Ignore changes in-between 'changes to use graphql-ws' and 'Fix tests'... I was trying to hack out some of the testing to cut down on JitPack loading time but fixing the tests for my graphql-ws purposes actually exposed something I missed :/
❤️ 1
:thank-you: 1
m

mbonnin

05/06/2021, 9:32 PM
Ah pretty cool!!
I'm trying to get that in a state where it can co-exists with the existing protocol
d

Doug Chappelle

05/06/2021, 9:45 PM
yeah.. I wasn't worried about that too much 😬
m

mbonnin

05/06/2021, 10:57 PM
Slightly confused by
-      webSocket.send(data)
        +      webSocket.send(String(data.toByteArray()))
Isn't that the same thing ultimately?
Like I'd expect OkHttp to encode the string to bytes before sending them to the wire?
Oooohh forget what I said, I just realized that WebSockets have type 1 and type 2 frames 👍
Interesting
d

Doug Chappelle

05/07/2021, 12:16 PM
👍
m

mbonnin

05/11/2021, 8:44 AM
That took a while but I finally landed something that I think would work with both protocols: https://github.com/apollographql/apollo-android/pull/3102/files#diff-8052d8e0b178f17902b10de1a636160ef67d4ae3ff96d64bed63b92a600a9641
Strangely, I didn't need to send the data as text, sending binary worked so there might be something to investigate there
d

Doug Chappelle

05/11/2021, 7:49 PM
I didn't need to send the data as text, sending binary worked
I think it's just our server is expecting text not binary. Would there be a way to configure this? I could give it a try on our backend.
m

mbonnin

05/11/2021, 7:51 PM
Gotcha. What is your server based on out of curiosity?
I'll make that configurable
d

Doug Chappelle

05/11/2021, 7:56 PM
Our server is a Java / Spring backend and actually implements the Graphql over WebSocket using graphql-ws protocol itself. Once configurable let me know and I'll give it a test drive 🙂
👍 1
m

mbonnin

05/12/2021, 1:30 PM
3.0.0-dev10
is out with configurable websocket protocol.
It defaults to
WsFrameType.Binary
. To change to text, use
GraphQLWsProtocol(frameType = WsFrameType.Text)
d

Doug Chappelle

05/12/2021, 2:21 PM
Awesome, I'll give it a try asap and let you know
👍 1
The previous ApolloWebSocketNetworkTransport constructor had a parameter connectionParams. I need this functionality... is there a way to do it with 3.0.0-dev10 ?
m

mbonnin

05/13/2021, 6:25 PM
I think it's still there for the
SubscriptionWsProtocol
but might have gone in
GraphqlWsProtocol
it should be relatively easy to add it, feel free to send a PR. I'm off today and tomorrow but can look into this on Monday
Worst case, you should be able to implement your own
WsProtocol
I'm hoping this API can accommodate more custom use case like graphql-ws and maybe appsync
d

Doug Chappelle

05/13/2021, 7:10 PM
It was very simple to add the connectionPayload in the GraphQLWsProtocol, thanks!
👍 1
m

mbonnin

05/17/2021, 8:56 AM
Made a PR there: https://github.com/apollographql/apollo-android/pull/3110. Just curious, what is it used for? Authentication I guess?
d

Doug Chappelle

05/17/2021, 3:23 PM
Correct, authentication purposes, thanks for the update!
👍 1
Hi @mbonnin, simple question... there is no 'unsubscribe' with the ApolloClient that I see.. can you tell me the proper way this should be done?
m

mbonnin

06/25/2021, 2:05 PM
It's all done using the coroutine APIs. If you want to cancel a request, you can wrap it in launch {} and cancel the job
job = launch {
  val response = apolloClient.query(request)
  // do something with response
}

// Cancel here if needed
job.cancel()
Most of the times, you should have a scope that will cancel the coroutine for you. For an example, on Android, the lifecycleScope should cancel the coroutine if you leave the Activity
👍 1
d

Doug Chappelle

07/09/2021, 6:26 PM
Hi @mbonnin, we have cases in which the socket will be closed e.g., the connection token is expired. In that case the Websocket shutdown code 4401: Unauthorized is sent by the backend. Is there a way to get those instead of the ApolloNetworkException (ACK not received after x amount of time)?
m

mbonnin

07/09/2021, 9:19 PM
Mmmm I would expect all the currently open subscription to receive an error. The
"ACK not received after x amount of time"
should come from another attempt to read the WebSocket. Maybe a
retry
?
d

Doug Chappelle

07/12/2021, 1:19 PM
I receive the "ACK not received after x amount of time" in the subscription.retryWhen block. However, I don't see the original error 🤔
m

mbonnin

07/12/2021, 1:23 PM
Any chance you can reproduce with https://github.com/martinbonnin/graphql-ws-server ?
d

Doug Chappelle

07/12/2021, 2:08 PM
I tried using your server. I don't see a connection error when I subscribe, I just see the "ACK not received..." message
m

mbonnin

07/12/2021, 2:09 PM
So no message received at all?
d

Doug Chappelle

07/12/2021, 2:11 PM
just the ACK message
m

mbonnin

07/12/2021, 2:12 PM
Mmmm that's unexpected
I'll double check this
👍 1
If you could modify
graphql-ws-server
to exhibit the error, that'd be super helpful
d

Doug Chappelle

07/12/2021, 3:07 PM
I'm afraid I don't know typescript at all 😬 but the test case would be to close the socket with a 4401 and see what your test client receives
m

mbonnin

07/12/2021, 3:11 PM
👍. Do you mind opening an issue for this? I can look into this but that'll take a bit of time so having an issue will make sure I don't forget
d

Doug Chappelle

07/12/2021, 3:30 PM
Hi @mbonnin, i'm trying to build v3.0.0-alpha01 but get unresolved reference build errors for CacheQueries & ApolloDatabase?
m

mbonnin

07/14/2021, 10:15 PM
That's a sqldelight issue
Remove your build folders and it should go away
d

Doug Chappelle

07/15/2021, 1:27 PM
I tried that but couldn't get it to work... removed and recloned the repo and it works now (I thought I removed all the build folders 🤔) . Now the plugin is complaining about my schema file... directive 'include' is defined multiple times but it is not. I read the migration guide but didn't see anything about this problem?
m

mbonnin

07/15/2021, 1:35 PM
Do you mind sharing your schema?
d

Doug Chappelle

07/15/2021, 1:40 PM
"Directs the executor to include this field or fragment only when the `if` argument is true"
directive @include(
    "Included when true."
    if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Directs the executor to skip this field or fragment when the `if`'argument is true."
directive @skip(
    "Skipped when true."
    if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
. . .
The schema is the same as I was using with v3.0.0-dev10
m

mbonnin

07/15/2021, 1:42 PM
I see. Since these are builtin we add them too. Let me see if there's something in the spec
When representing a GraphQL schema using the type system definition language any built-in directive may be omitted for brevity.
That's a may, not a must so we should be robust to that. I'll make a fix
In between, you can safely remove them
👍 1
d

Doug Chappelle

07/15/2021, 2:23 PM
works great!
👍 1
m

mbonnin

07/15/2021, 3:45 PM
Also. I love this thread but feel free to open new ones for new errors/topics. This way, other people in the channel get a chance to chip in too
d

Doug Chappelle

07/15/2021, 5:08 PM
I'll start using a new thread 🙂 but the unresolved reference build errors for CacheQueries & ApolloDatabase happens after a gradlew clean is done... and I went through to check all build folders are gone 😞
m

mbonnin

07/15/2021, 5:37 PM
Usually it's the
apollo-normalized-cache-sqlite
. Doing
rm -rf apollo-normalized-cache-sqlite/build
usually fixes it