Does anyone have a working example of using `ktor`...
# multiplatform
a
Does anyone have a working example of using
ktor
in MPP environment, specifically on iOS where currently only Main-Thread Dispatcher is offered (https://github.com/Kotlin/kotlinx.coroutines/issues/462)?
j
Sessionize is my to-go example of a mpp project with ktor and background tasks: https://github.com/touchlab/DroidconKotlin
r
I suppose you either lack documentation or have a technical issue, expanding a little bit on what you need may help
a
I've got the understanding that I need iOS-code dispatcher in order to coroutines to work at all in iOS:
Copy code
private class IODispatcher: CoroutineDispatcher() {
    val queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND.toLong(), 0)
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(queue) {
            block.run()
        }
    }
}

actual class MainScope: CoroutineScope {
    private val dispatcher = MainDispatcher()
    private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = dispatcher + job
}
Then I use
MainScope().launch {     }
to execute ktor requests. But then obviously, each request will be executed on main thread, which I do not want
r
Well the ktor client for iOS works
a
Yes, I admit it works, except I want to make sure it does not work on main thread.
r
I don’t work on the iOS side a lot, isn’t the app supposed to crash if you do IO on main thread?
a
No, android crashes in this case, iOS not 😕
r
I’m not sure how it works then. It at least doesn’t freeze the UI while the request is going. You can have a lot of coroutines running on a single thread.
a
Well I'd like to see a piece of code which executes ktor request on iOS. Means which coroutine scope/context is used.
r
If I’m not mistaken a network request mostly takes time waiting for IO to happen, it doesn’t use a lot of other resources. The coroutine responsible for the request should suspend while waiting, and a suspended coroutine uses basically no resources. That’s what I understand and I may be completely wrong
Here is our base presenter on iOS
Copy code
abstract class BasePresenter<T: Any>(viewRef: T) : CoroutineScope {

    private var job = Job()

    override val coroutineContext: CoroutineContext
        get() = UI() + job

    fun onDestroy() {
        job.cancel()
    }

}
The UI class
Copy code
class UI : CoroutineDispatcher(), Delay {

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        val queue = dispatch_get_main_queue()
        dispatch_async(queue) {
            block.run()
        }
    }

    @ExperimentalCoroutinesApi
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeMillis * 1_000_000), dispatch_get_main_queue()) {
            with(continuation) {
                resumeUndispatched(Unit)
            }
        }
    }

    override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
        val handle = object : DisposableHandle {
            var disposed = false
                private set

            override fun dispose() {
                disposed = true
            }
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeMillis * 1_000_000), dispatch_get_main_queue()) {

            if (!handle.disposed) {
                block.run()
            }

        }
        return handle
    }
}
a
I understand both of them, an where you are calling something like
Copy code
suspend fun getKPIData() : String {
        val params = HashMap<String, String>()
        return client.get<String> {
            url(constructURL("kpis", params))
        }
    }
r
Well in the presenter we just do
launch { }
and call our suspend function in there
It’s basically the first time I see this code and I see some errors
I think the job should be a
SupervisorJob
for example
a
Thanks, this brings be further. From what I understand about coroutines now, with the code above launch{} will be called at main thread (I have even verified it on Android). Then, how it works at all? This is what's puzzling me
r
I think of it a bit like NodeJS. NodeJS runs on a single thread but is still async. Same thing with coroutines. You only need multiple threads if you’re actually doing CPU work
s
K/N coroutines are currently only support running on the main thread. The concurrency model between K/N and JVM Kotlin is very different and there are things yet to work out.
a
That meas ktor requests on K/N will run on main thread ?
j
Yes, but making ktor requests on main thread works just fine (ie. no UI freezing nor crashes)
a
@ribesg I think the difference between yours and ours project is that you have iOS implementation only, right? I'm trying to run the same code on both iOS and Android so such class UI: CoroutineDispatcher() does not exist for Android, I need to make it truly MPP.
@Jan Stoltman thanks, if it would work I would take it for now until better solution is found. Just currently I get
kotlin.TypeCastException
while trying to use solution as above
r
I do have code running on both platforms, and code specific to platforms. Your view controllers aren’t mpp, are they?
A big part of our application is split in multiple mpp libraries, but the app themselves are different projects. The UI is very close but different enough to justify having different presenters on this app
a
My ViewControllers aren't MPP, yes. But your BasePresenter example above should be iOS code, right? What the counterpart of Android looks like (do you have implementation of class UI : CoroutineDispatcher() on Android) ?
r
No, on Android the implementation exists already
Copy code
abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() {
On Android the view is the scope, on iOS the presenter is the scope because the view is Swift
Oh, I also don’t remember why but it’s better to use
coroutineContext.cancelChildren()
in your
onDestroy()
rather than just
cancel()
a
Ok I finally see. You call ktor requests from platform code. I'm trying to do this from MPP code 😕
r
No I’m calling ktor from mpp code
And the code calling ktor can be called from either platform
You can call ktor from any suspending function, as a suspending function inherits the coroutine context it was called in
I have libraries who basically declare suspending functions calling ktor, these libraries are then used in an Android app and an iOS app.
c
I feel like I’m missing something here. I’m trying to get Ktor to work on iOS and the solutions y’all have posted seem to throw mutability errors. Do you have a complete example project I might take a look at here?
I’m getting
mutation attempt of frozen io.ktor.client.request.HttpRequestPipeline
on Kotlin 1.3.31, ktor 1.1.4, coroutines 1.2.0. Are there different versions that are known to work?
u
This is while using your own dispatcher? This is expected 😄 As stated above, you can safely execute your ktor calls in a coroutine on the main thread. The coroutine will send the request on the main thread, suspend to let other code run and will be resumed to process the result when it is available. Just like a call back approach. Called on the main thread, called back on the main thread, but no blocking in-between
c
How do I execute it on the main thread? I’m getting the same issue with all the provided
Dispatchers
, and creating it like any of the above solutions also fails (despite them saying that it works)
r
@Casey Brooks your problem is that your ktor client is frozen. I got this one too. A lot of people got it. Just don’t define your client in something that’ll get frozen: for example you can’t have one inside an
object
(or inside an instance of something that is inside an
object
, etc)
c
Wow, that was it, I finally got this working! Thank you so much!