Search code examples
kotlinkotlin-multiplatformktorktor-client

Unable to download file using Ktor in Kotlin Multiplatform


I'm trying to download a file from the internet using Ktor.

I've tried using the following code:

val response = client.get(downloadLink) {
    onDownload { bytesSentTotal, contentLength ->
        println("Downloaded $bytesSentTotal of $contentLength")
    }
}.bodyAsChannel()

The complete code is here. This is running on a Pixel 7 with Android 14 beta 1.1.

In this example, downloadLink is this. The file is an empty text file (named test.txt). I've also tried downloading a text file with contents, but the same error occurs. What am I doing wrong?

FATAL EXCEPTION: main
                                                                                                   
Process: nl.tiebe.otarium, PID: 13700
                                                                                                   
io.ktor.client.call.NoTransformationFoundException: No transformation
found: class io.ktor.utils.io.ByteBufferChannel (Kotlin reflection is
not available) -> class kotlinx.serialization.json.JsonObject (Kotlin
reflection is not available)
                                                                                                   
with response from https://files.magister.net/file?md5=qEbVw5h-Pn0GvwaZrDpafw&expires=1682650029&m=dGV4dC9wbGFpbg&f=dGVzdC50eHQ&p=ZmlsZWJsb2IvbnVvdm8vM2Q2NjkxMjMtMTZiNS00MTYxLThiNjgtODYzZTFmNDk2OGM2L2ZpbGU&d=YXR0YWNobWVudA:
                                                                                                   
status: 200 
                                                                                                   
response headers: 
                                                                                                   
accept-ranges: bytes
, content-disposition: attachment; filename=test.txt
, content-length: 0
, content-type: application/octet-stream
, date: Fri, 28 Apr 2023 01:47:09 GMT
, etag: "d20e008ed58d72511427b6538372a7c4"
, last-modified: Wed, 26 Apr 2023 11:07:53 GMT
, server: nginx
, strict-transport-security: max-age=31536000
    at io.ktor.client.call.HttpClientCall.bodyNullable(HttpClientCall.kt:93)
    at io.ktor.client.call.HttpClientCall.body(HttpClientCall.kt:114)
    at dev.tiebe.magisterapi.api.messages.MessageFlow.getDownloadLink(MessageFlow.kt:179)
    at dev.tiebe.magisterapi.api.messages.MessageFlow$getDownloadLink$1.invokeSuspend(Unknown Source:15)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:68)
    at kotlinx.coroutines.internal.DispatchedContinuation.resumeWith(DispatchedContinuation.kt:345)
    at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:135)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:109)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:11)
    at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:59)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at android.os.Handler.handleCallback(Handler.java:958)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8180)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:946)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@4910176, Dispatchers.Main.immediate]

Solution

  • Turns out my function for retrieving the correct download link was incorrect. I used the following code for downloading the file in the end:

    val response = client.get(attachment.links.downloadLink.href) { // make sure this is the correct link!
        onDownload { bytesSentTotal, contentLength ->
            println("Downloaded $bytesSentTotal of $contentLength")
        }
    }.bodyAsChannel()