https://kotlinlang.org logo
#ktor
Title
# ktor
a

Anton Afanasev

08/23/2021, 12:28 PM
Hello, I am having some issues with new
onUpload
API on KMM platform. Android works fine, but iOS produce an
InvalidMutabilityException
.I am looking for any feedback from the community. I am using ktor = 1.6.0
Copy code
suspend fun uploadFile(
        url: String,
        byteArray: ByteArray,
        progressCallback: (Float) -> Unit
    ) {
        client.put<Unit>(url) {
            onUpload { bytesSendTotal: Long, contentLength: Long ->
                progressCallback((bytesSendTotal / contentLength.toFloat()) * 100)
            }
            body = byteArray
        }
    }
I call it from another class that is responsible for managing states of the uploaded object. Dispatcher:
Copy code
private val uploadDispatcher = CoroutineScope(Dispatchers.Main + SupervisorJob())
Call to the function:
Copy code
uploadDispatcher.launch { api.uploadFile(url, byteArray) { progress ->
    data = data.copy(state = Uploading(progress))
        .also(onUploadProgress)
    }
}
where the
onUploadProgress
is lambda:
Copy code
private val onUploadProgress: (Data) -> Unit,
As mentioned above, while executing this code on iOS I am receiving an
InvalidMutabilityException
Looking into Ktor, it looks like the
Copy code
UploadProgressListenerAttributeKey
marked with
@SharedImmutable
which makes it deeply frozen and freeze the objects it refers to. Considering that when upload progress change I need to wrap and update state sof my internal objects - how should I do that using provided
onUpload
API?
i

Igor Maric

08/23/2021, 2:16 PM
Same here, I tried using even newer version but it is the same.
1
a

Aleksei Tirman [JB]

08/23/2021, 2:36 PM
@Anton Afanasev could you please share definitions for symbols in?
Copy code
data = data.copy(state = Uploading(progress))
    .also(onUploadProgress)
}
a

Anton Afanasev

08/23/2021, 2:42 PM
In my code
data
is actually a mutable map:
Copy code
private val dataMap = mutableMapOf<String, Data>()

@Serializable
private class Data(
    val state: Data.State = Data.State.Idle,
    val id: String? = null,
    val fileName: String,
    var byteArray: ByteArray,
    var job: Job? = null,
) {

 @Serializable
    sealed class State {
        object Idle : State()
        object Deleted : State()
        object Detached : State()
        object Presigning : State()
        data class Uploading(val progress: Float) : State()
        data class Uploaded(val downloadUrl: String) : State()
        data class Sent(val downloadUrl: String) : State()
    }
}
I am using
Data
to report changes with this object. Potentially there are can be multiple
data
objects handle in the same time.
progress
is the outcome of progressCallback that is invoked every ktor.onUpdate() and simply convert the progress to percentage amount
a

Aleksei Tirman [JB]

08/23/2021, 2:51 PM
Could you please simplify your code to make
InvalidMutabilityException
reproducible without using definitions for all those classes?
a

Anton Afanasev

08/23/2021, 2:52 PM
Sure, let me do some small demo which reproduce the issue.
Hi @Aleksei Tirman [JB] I made a small demo with downloadProgress. You can find it here. https://github.com/AfanasievAnton/Ktor-Download-POC.git Also I was wondering about
UploadProgressListenerAttributeKey
and
DownloadProgressListenerAttributeKey
in io.ktor.client.features They annotated with @SharedImmutable. Wondering if it is necessary, as apparently this is the reason why my own listener and data structure that handle onUpload/onDownload become frozen as well. And since I am trying to mutate them I got an In
InvalidMutabilityException
. Thank you in advance
a

Aleksei Tirman [JB]

08/25/2021, 8:54 AM
@Anton Afanasev thank you for the sample project. Indeed, there is no way to mutate objects in a
onDownload
lambda if those objects were defined outside of it. I've created an issue KTOR-3068 to address this problem. Unfortunately, I don't know how to work around this issue.
r

russhwolf

08/25/2021, 12:26 PM
That sample will work if you make
flag
atomic
a

Anton Afanasev

08/25/2021, 1:01 PM
@Aleksei Tirman [JB] Thanks for your response. I would like to point that this limitation is relevant for both
onDownload
and
onUpload
listeners.
@russhwolf It will work But as Ktor consumer I might have a bunch of dependencies involved in OnDownlod/OnUpload progress report. And I will have to convert all of them to atomic. Unless there are some tricks to avoid it.
r

russhwolf

08/27/2021, 1:29 PM
Yeah, the native memory model takes some getting used to. Stately has some freeze-safe data structures which can help. https://github.com/touchlab/Stately
👍 1
15 Views