Has anyone here ever made a Subscription work on i...
# apollo-kotlin
n
Has anyone here ever made a Subscription work on iOS under Kotlin Native willing to share an example of how they are doing it? I've time boxed myself to figure this out this morning, and I've failed. So, hoping someone else has done this. Thanks in advance.
m
That might not exactly be what you're looking since it's for MacOS and based on
dev-3.x
but I just added a subscription test that makes K/N subscriptions against the tutorial server: https://github.com/apollographql/apollo-android/pull/3102/files#diff-93890a9196f59ee3f458c788b6039f0c6e7fd0a3021fe5d3f43ccbf56537e88e
n
The
runWithMainLoop
sections in the tests are likely why they work. I'm going to work on a super small sample that shows the problems that I'm having with this. I either get illegal dereference, or the frozen access exceptions and I have no idea why this would be happening when the non subscription (mutations and queries) code works fine. I'm definitely missing something.
m
Oh yea, everything in the iOS code assumes coroutines run from the main loop
If you're running
coroutines-mt
that might the issue
n
Definitely running
coroutines-mt
but it works for queries and mutations. The difference I guess is the web socket setup.
The link you shared above is pretty much exactly what I'm running into, but not experienced enough to know how to solve yet. 🙂
m
What I don't 100% understand yet is why you would need to reference
ApolloWebSocketNetworkTransport
from something else than the main thread?
n
So, the current error I have is this:
Copy code
Exception doesn't match @Throws-specified class list and thus isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
Uncaught Kotlin exception: kotlin.native.concurrent.FreezingException: freezing of Ok(result=<no name provided>_1@23ff648) has failed, first blocker is com.apollographql.apollo.network.ws.ApolloWebSocketNetworkTransport@b4288
And I know not really informative. The setup for this is pretty detailed. I'll throw my relevant code bits here.
m
Nice, that'd be super helpful 🙏
n
This is the poor-mans DI. Really, the main thread of the iOS app sets the token provider which also creates the graphql client. This is my attempt at trying to make the objects on one thread, but it still doesn't work.
This is the code that gets called to perform the subscription. Here the apollo client is obtained from the graph, and doesn't appear to be 'frozen' yet. But it gets frozen at some point in here. Not sure why.
m
You don't seem to be creating any threads there ?
In theory, you don't need an
IsolateState
for
providedValues
as all this code should run from the main thread
n
Okay, will give that a try.
m
What are you using at the moment out of curiosity?
does
coroutines-mt
provide
Dispatchers.Main
?
n
Yes, I am using
coroutines-mt
and
flowOn(Dispatchers.Main)
. I seem to be able to make a flow in place, and those elements get delivered to iOS as expected, but as soon as I replace the flow with an Apollo subscription, I get the frozen errors.
I looked at MainLoopDispatcher, which is implemented in iOSMain. So, guessing I'd have to have an expect API that would return that so that commonMain code can use it.
m
Yea most likely, you can have
actual val myMainDispatcher = MainLoopDispatcher
on iOS and use Dispatchers.Main on Android
n
Yeah. Working through that. The
invokeOnTimeout
has a different method signature now. Using 1.4.3-mt.
m
That's interesting!
I wonder what's different. I would expect the coroutines-mt dispatcher to be relatively identical 🤔
checking out the coroutines repo...
Looks like the main difference is that the coroutines-mt dispatcher supports
immediate
mode
Besides that, it's all
Copy code
dispatch_async(dispatch_get_main_queue()) {
            block.run()
        }
n
Okay, about ready to test this, had to propagate
@InternalCoroutinesApi
all over the place to get it compiling.
m
Arg, rereading your last sentence, I thought it was working 🤦‍♂️
Ok so I expect this to fail in the same way given how similar the two dispatchers are
n
Yes, it does. Exactly the same error. I'm going to lift the apollo client creation back out of the IsolateState, and see what that does.
m
I would remove the IsolateState completely, I don't think you need it
All your DI runs from the main thread
(or at least this is what I understand how it should work)
n
Yeah, it really should, but I needed it for the token provider, which gets called during the network interception.
m
The
BearerTokenInterceptor
being an
ApolloRequestInterceptor
, will also get called from the main loop
n
Anyway, removing the graphql client creation from the IsolateState doesn't make a difference. It still gives me freezing errors.
m
Everything is from the main loop 😅
Now the interceptors have a
suspend fun intercept
which is where you'd do the "async part" because you don't want to block the main thread there
How do you refresh the token?
n
This is done in app space, Firebase ID token.
m
👍
n
Not brave enough to do firebase auth in KMP land.
😂 1
m
😅
Bottom line, if you're getting the token from the main thread as well, you should be able to remove the
IsolateState
n
Fair enough. For whatever reason, it was also frozen. Graph is a kotlin object, so maybe it's just freezing everything it touches?
Copy code
singleton objects unless marked with @kotlin.native.ThreadLocal are frozen and shared, lazy values allowed, unless cyclic frozen structures were attempted to be created
What bugs me a bit is this:
Copy code
providedValues.access {
            it.put(TOKEN_PROVIDER, provider)
            it.put(APOLLO_CLIENT, makeApolloClient())
        }
IIRC,
IsolateState
will run the
access {}
block in its own isolated worker
Which means the
ApolloClient
is created from a background thread
n
Yes, I have removed that now. The apolloClient is now made in a lazy {} block.
m
There's a chance that
Copy code
val apolloClient: ApolloClient
        get() = providedValues.access {
            it[APOLLO_CLIENT] as ApolloClient
        }
freezes the client
n
Updated...
m
🥁
Is it any better?
n
No.
m
😞
n
Honestly, at this point, I wonder if some sort of video call would be something you'd entertain. Otherwise, I'll work on this some other time.
m
I'm happy to say hello but I'm most likely going to say the same things as here 🙂
Also it's super late here and I should probably stop working
n
Yeah, no worries. I'll let you know how I fare. Going to try a few more things. It's only lunch time here.