https://kotlinlang.org logo
Title
n

Neal Sanche

05/06/2021, 5:32 PM
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

mbonnin

05/11/2021, 8:49 AM
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

Neal Sanche

05/11/2021, 5:10 PM
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

mbonnin

05/11/2021, 5:11 PM
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

Neal Sanche

05/11/2021, 5:13 PM
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

mbonnin

05/11/2021, 5:22 PM
What I don't 100% understand yet is why you would need to reference
ApolloWebSocketNetworkTransport
from something else than the main thread?
n

Neal Sanche

05/11/2021, 5:42 PM
So, the current error I have is this:
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

mbonnin

05/11/2021, 5:44 PM
Nice, that'd be super helpful 🙏
n

Neal Sanche

05/11/2021, 5:47 PM
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

mbonnin

05/11/2021, 5:51 PM
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

Neal Sanche

05/11/2021, 6:05 PM
Okay, will give that a try.
m

mbonnin

05/11/2021, 6:07 PM
What are you using at the moment out of curiosity?
does
coroutines-mt
provide
Dispatchers.Main
?
n

Neal Sanche

05/11/2021, 6:15 PM
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

mbonnin

05/11/2021, 6:20 PM
Yea most likely, you can have
actual val myMainDispatcher = MainLoopDispatcher
on iOS and use Dispatchers.Main on Android
n

Neal Sanche

05/11/2021, 6:23 PM
Yeah. Working through that. The
invokeOnTimeout
has a different method signature now. Using 1.4.3-mt.
m

mbonnin

05/11/2021, 6:24 PM
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
dispatch_async(dispatch_get_main_queue()) {
            block.run()
        }
n

Neal Sanche

05/11/2021, 6:31 PM
Okay, about ready to test this, had to propagate
@InternalCoroutinesApi
all over the place to get it compiling.
m

mbonnin

05/11/2021, 6:33 PM
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

Neal Sanche

05/11/2021, 6:34 PM
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

mbonnin

05/11/2021, 6:35 PM
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

Neal Sanche

05/11/2021, 6:38 PM
Yeah, it really should, but I needed it for the token provider, which gets called during the network interception.
m

mbonnin

05/11/2021, 6:40 PM
The
BearerTokenInterceptor
being an
ApolloRequestInterceptor
, will also get called from the main loop
n

Neal Sanche

05/11/2021, 6:40 PM
Anyway, removing the graphql client creation from the IsolateState doesn't make a difference. It still gives me freezing errors.
m

mbonnin

05/11/2021, 6:40 PM
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

Neal Sanche

05/11/2021, 6:45 PM
This is done in app space, Firebase ID token.
m

mbonnin

05/11/2021, 6:45 PM
👍
n

Neal Sanche

05/11/2021, 6:45 PM
Not brave enough to do firebase auth in KMP land.
😂 1
m

mbonnin

05/11/2021, 6:46 PM
😅
Bottom line, if you're getting the token from the main thread as well, you should be able to remove the
IsolateState
n

Neal Sanche

05/11/2021, 6:48 PM
Fair enough. For whatever reason, it was also frozen. Graph is a kotlin object, so maybe it's just freezing everything it touches?
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:
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

Neal Sanche

05/11/2021, 6:53 PM
Yes, I have removed that now. The apolloClient is now made in a lazy {} block.
m

mbonnin

05/11/2021, 6:54 PM
There's a chance that
val apolloClient: ApolloClient
        get() = providedValues.access {
            it[APOLLO_CLIENT] as ApolloClient
        }
freezes the client
n

Neal Sanche

05/11/2021, 6:54 PM
Updated...
m

mbonnin

05/11/2021, 6:55 PM
🥁
Is it any better?
n

Neal Sanche

05/11/2021, 6:55 PM
No.
m

mbonnin

05/11/2021, 6:55 PM
😞
n

Neal Sanche

05/11/2021, 6:56 PM
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

mbonnin

05/11/2021, 6:58 PM
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

Neal Sanche

05/11/2021, 6:59 PM
Yeah, no worries. I'll let you know how I fare. Going to try a few more things. It's only lunch time here.