BollywoodVillain
11/25/2020, 12:54 PMkotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen
. It works fine on Android and gets the network data without issues. I created a brand new MPP project using Android Studio MPP template and added the Ktor network code from Ktor-sample/client-mpp repo. What could be the problem?
The complete project is on Github at: https://github.com/samkhawase/Kotlin_MPP_Demo.
I’m using ktor 1.4.2
, Kotlin 1.4
, Kotlin plugin 1.4.20-release-Studio4.1.1
, and KMM plugin 0.2.0-release-65-Studio4.1
.
Here’s a snapshot of my sourceSets
from build.gradle.kt
(:shared)
val ktor_version = "1.4.2"
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")
}
}
val commonTest by getting {
dependencies {
//...
implementation("io.ktor:ktor-client-core:$ktor_version")
}
}
val androidMain by getting {
dependencies {
//...
implementation("io.ktor:ktor-client-android:$ktor_version")
implementation("io.ktor:ktor-client-serialization-jvm:$ktor_version")
}
}
val androidTest by getting {
dependencies {
//...
implementation("io.ktor:ktor-client-android:$ktor_version")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")
}
}
val iosTest by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")
}
}
}
Here’s the failing Swift code
func getLocations() {
let apiService = ApiService()
apiService.about { (htmlString) in
print("🦋 htmlString:\n \(htmlString)")
}
}
struct ContentView: View {
var body: some View {
Text(greet()).onAppear {
print("isMainThread: \(Thread.isMainThread)")
getLocations()
}
}
}
Here’s the complete stacktrace from the iOS app.Cicero
11/25/2020, 12:56 PMisMainThread: true
Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
@Throws(Exception::class)
suspend fun getMeIOS(token: String): Me? {
return client.get<Me> {
url("https://")
accept(ContentType.Application.Json)
contentType(ContentType.Application.Json)
header(
"Authorization",
token
)
}
}
BollywoodVillain
11/25/2020, 12:58 PMCicero
11/25/2020, 12:58 PMBollywoodVillain
11/25/2020, 12:58 PMsuspend
in the ktor-samplesCicero
11/25/2020, 12:59 PMBollywoodVillain
11/25/2020, 12:59 PMpackage io.ktor.samples.mpp.client
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.*
internal expect val ApplicationDispatcher: CoroutineDispatcher
class ApplicationApi {
private val client = HttpClient()
var address = Url("<https://tools.ietf.org/rfc/rfc1866.txt>")
fun about(callback: (String) -> Unit) {
GlobalScope.apply {
launch(ApplicationDispatcher) {
val result: String = client.get {
url(this@ApplicationApi.address.toString())
}
callback(result)
}
}
}
}
Cicero
11/25/2020, 1:00 PMBollywoodVillain
11/25/2020, 1:00 PMlet me give you a sample of how I unwrap this on my frontmuch appreciated! I was banging my head traversing the interwebz for this error
And you believe them?Us newbies have no other choice 😅
Cicero
11/25/2020, 1:01 PMBollywoodVillain
11/25/2020, 1:05 PMApiService
class, not when I call the suspend func. Let me check onceApiService
variable on Swift.Cicero
11/25/2020, 1:10 PMArkadii Ivanov
11/25/2020, 1:10 PMensureNeverFrozen()
into its init
section, so you will get a stack trace of the place, where it is actually gets frozen.Cicero
11/25/2020, 1:10 PMsourceSets {
val ktorVersion = "1.4.2"
val serializationVersion = "1.0.0-RC"
val coroutineVersion = "1.3.9-native-mt-2"
val commonMain by getting {
val commonCore = "io.ktor:ktor-client-core:$ktorVersion"
val commonJson = "io.ktor:ktor-client-json:$ktorVersion"
val commonSerialization = "io.ktor:ktor-client-serialization:$ktorVersion"
val commonSerializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion"
val common = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
dependencies {
implementation(commonCore)
implementation(commonSerializationCore)
implementation(commonJson)
implementation(commonSerialization)
implementation(common)
}
}
val androidMain by getting {
val ktorAndroid = "io.ktor:ktor-client-android:${ktorVersion}"
dependencies {
implementation(ktorAndroid)
}
}
val iosMain by getting{
val ktoriOS = "io.ktor:ktor-client-ios:${ktorVersion}"
dependencies {
implementation(ktoriOS)
}
}
}
private val json = Json {
isLenient = true; ignoreUnknownKeys = true; coerceInputValues = true; useArrayPolymorphism =
true
}
internal val client = HttpClient() {
install(JsonFeature) {
serializer = KotlinxSerializer(json)
}
}
Arkadii Ivanov
11/25/2020, 1:13 PMjson
field, create the JSON directly in HttpClient lambdaCicero
11/25/2020, 1:14 PMBollywoodVillain
11/25/2020, 1:15 PMApiService.kt
is the culprit:
url(this@ApiService.address.toString())
Cicero
11/25/2020, 1:15 PMBollywoodVillain
11/25/2020, 1:16 PMCicero
11/25/2020, 1:16 PMBollywoodVillain
11/25/2020, 1:17 PMCicero
11/25/2020, 1:17 PMArkadii Ivanov
11/25/2020, 1:19 PMBollywoodVillain
11/25/2020, 1:20 PMLooks like HttpClient lambda freezes the captured ApiService object. Try to avoid theThat’s what I’m trying. Thanksfield, create the JSON directly in HttpClient lambdajson
You can addstrangely android studio can’t findinto itsensureNeverFrozen()
sectioninit
ensureNeverFrozen()
reference when I do this:
class ApiService {
init {
ensureNeverFrozen()
}
//...
}
Arkadii Ivanov
11/25/2020, 1:21 PMBollywoodVillain
11/25/2020, 1:22 PMYou need expect/actual itDo you have any pointers on how to do this?
expect/actual
Arkadii Ivanov
11/25/2020, 1:25 PMBollywoodVillain
11/25/2020, 1:27 PMexpect/actual
is quite cool, much better than what I had to do to get C++ interop with iOS/Android.Arkadii Ivanov
11/25/2020, 1:29 PMInvalidMutabilityException
, it's shows you the place where the mutation attempt of a frozen object happened. If you you add ensureNeverFrozen
to that class, you will get another exception earlier, on freeze attempt.nrobi
11/25/2020, 1:29 PMpreventFreeze()
, which is essentially the same expect/actual
and uses ensureNeverFrozen
on the native partBollywoodVillain
11/25/2020, 1:30 PMCicero
11/25/2020, 1:33 PMMichal Klimczak
11/30/2020, 1:15 PMprivate val kotlinxSerializer = KotlinxSerializer(jsonConfig)
private val httpClient: HttpClient = HttpClient {
install(JsonFeature) {
serializer = kotlinxSerializer
}
}
If I remove serializer = kotlinxSerializer
, it doesn't crash.
(Also if I add preventFreeze
, the project doesn't compile at all with Command PhaseScriptExecution failed with a nonzero exit code
)
I have all ktor dependencies on 1.4.2
and corotuines at 1.4.2-native-mt
.val ktorVersion = "1.4.1" //"1.4.2"
val coroutineVersion = "1.3.9-native-mt-2" //"1.4.2-native-mt-2"
val kotlinxSerialization = "1.0.0-RC2" //"1.0.1"
BollywoodVillain
12/07/2020, 12:06 PMKotlinXSerializer
init(). You can find the final working source code here:
https://github.com/samkhawase/Kotlin_MPP_DemoMichal Klimczak
12/07/2020, 12:09 PMBollywoodVillain
12/07/2020, 12:30 PMKotlinxSerializer
inside the install(JsonFeatue){
block. You can see my ApiSErvice.kt
file for reference.Michal Klimczak
12/07/2020, 1:20 PM