I am developing a KMM project and the authentication works well on the Android app. However when I added the Auth feature in the httpclient (located in the shared.commonMain) the ios app failled at runtime with the following message
Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError. It is considered unexpected and unhandled instead. Program will be terminated.
This is how I create the httpclient
private val httpclient = HttpClient() {
engine {
pipelining = true
threadsCount = 4
}
install(Logging) {
level = LogLevel.HEADERS
logger = object : Logger {
override fun log(message: String) {
Napier.v(tag = "HTTP Client", message = message)
}
}
}
install(JsonFeature) {
val json = Json { ignoreUnknownKeys = true }
serializer = KotlinxSerializer(json)
}
install(Auth) {
basic {
credentials {
BasicAuthCredentials(username = emailUser, password = passwordUser)
}
}
}
}.also {
initLogger()
}
Here is the complete code of the Greeting class:
class Greeting {
private var emailUser: String = ""
private var passwordUser: String = ""
private val httpclient = HttpClient() {
engine {
pipelining = true
threadsCount = 4
}
install(Logging) {
level = LogLevel.HEADERS
logger = object : Logger {
override fun log(message: String) {
Napier.v(tag = "HTTP Client", message = message)
}
}
}
install(JsonFeature) {
val json = Json { ignoreUnknownKeys = true }
serializer = KotlinxSerializer(json)
}
install(Auth) {
basic {
credentials {
BasicAuthCredentials(username = emailUser, password = passwordUser)
}
}
}
}.also {
initLogger()
}
@Throws(Exception::class)
suspend fun getVaccines(): List<Vaccine> {
return httpclient.get(endpointBase + Vaccine.path)
}
@Throws(Exception::class)
suspend fun loginUser(email: String, password: String): String? {
emailUser = email
passwordUser = password
return httpclient.get(endpointBase + User.path + "/userPage")
}
}
Exception full stacktrace
Function doesn't have or inherit @Throws annotation and thus exception 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.InvalidMutabilityException: mutation attempt of frozen com.example.vaccinationcertificate_mobileapp.Greeting@3963788
at 0 iosApp 0x000000010c1f728f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95
at 1 iosApp 0x000000010c1efbbd kfun:kotlin.Exception#<init>(kotlin.String?){} + 93
at 2 iosApp 0x000000010c1efe2d kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93
at 3 iosApp 0x000000010c2272fd kfun:kotlin.native.concurrent.InvalidMutabilityException#<init>(kotlin.String){} + 93
at 4 iosApp 0x000000010c228aff ThrowInvalidMutabilityException + 431
at 5 iosApp 0x000000010c3db2c0 MutationCheck + 128
at 6 iosApp 0x000000010c151165 kfun:com.example.vaccinationcertificate_mobileapp.Greeting#<init>(){} + 437
at 7 iosApp 0x000000010c17c9de objc2kotlin.883 + 142
at 8 iosApp 0x000000010c150fa3 $sSo14SharedGreetingCABycfcTO + 19
at 9 iosApp 0x000000010c14f0af $sSo14SharedGreetingCABycfC + 31
at 10 iosApp 0x000000010c150596 $s6iosApp11ContentViewVACycfC + 38 (/Users/oprisvlad2/projects/VaccinationCertificate/VaccinationCertificate-mobileapp/iosApp/iosApp/ContentView.swift:4:0)
at 11 iosApp 0x000000010c14ebf3 $s6iosApp6iOSAppV4bodyQrvgAA11ContentViewVyXEfU_ + 35 (/Users/oprisvlad2/projects/VaccinationCertificate/VaccinationCertificate-mobileapp/iosApp/iosApp/iOSApp.swift:7:4)
at 12 iosApp 0x000000010c14eda0 $s6iosApp11ContentViewVIgo_ACIegr_TR + 16
at 13 iosApp 0x000000010c14edd1 $s6iosApp11ContentViewVIgo_ACIegr_TRTA + 17
at 14 SwiftUI 0x00000001173612cf $s7SwiftUI11WindowGroupV7contentACyxGxyXE_tcfC + 63
at 15 iosApp 0x000000010c14eac5 $s6iosApp6iOSAppV4bodyQrvg + 181 (/Users/oprisvlad2/projects/VaccinationCertificate/VaccinationCertificate-mobileapp/iosApp/iosApp/iOSApp.swift:6:3)
at 16 iosApp 0x000000010c14ef79 $s6iosApp6iOSAppV7SwiftUI0B0AadEP4body4BodyQzvgTW + 9
at 17 SwiftUI 0x0000000116dce845 $s7SwiftUI15AppBodyAccessor33_A363922CEBDF47986D9772B903C8737ALLV06updateD02of7changedyx_SbtF0D0QzyXEfU_TA + 22
at 18 SwiftUI 0x0000000117357449 $s7SwiftUI12BodyAccessorPAAE03setC0yy0C0QzyXEFAFyXEfU_ + 34
at 19 SwiftUI 0x0000000116dce174 $s7SwiftUI15AppBodyAccessor33_A363922CEBDF47986D9772B903C8737ALLV06updateD02of7changedyx_SbtF + 1310
at 20 SwiftUI 0x00000001173575ac $s7SwiftUI10StaticBody33_49D2A32E637CD497C6DE29B8E060A506LLV11updateValueyyF + 161
at 21 SwiftUI 0x000000011754055c $s14AttributeGraph0A0VyACyxGqd__c5ValueQyd__RszAA12StatefulRuleRd__lufcADSPyqd__GXEfU_ySv_So11AGAttributeatcyXEfU_ySv_AJtcqd__mcfu_ySv_AJtcfu0_TA + 26
at 22 AttributeGraph 0x0000000110585e9b _ZN2AG5Graph11UpdateStack6updateEv + 553
at 23 AttributeGraph 0x0000000110586491 _ZN2AG5Graph16update_attributeENS_4data3ptrINS_4NodeEEEj + 411
at 24 AttributeGraph 0x000000011058c491 _ZN2AG5Graph20input_value_ref_slowENS_4data3ptrINS_4NodeEEENS_11AttributeIDEjPK15AGSwiftMetadataRhl + 299
at 25 AttributeGraph 0x00000001105a2889 AGGraphGetValue + 210
at 26 SwiftUI 0x00000001173574d5 $s7SwiftUI10StaticBody33_49D2A32E637CD497C6DE29B8E060A506LLV9container9ContainerQzvg + 67
at 27 SwiftUI 0x0000000117357599 $s7SwiftUI10StaticBody33_49D2A32E637CD497C6DE29B8E060A506LLV11updateValueyyF + 142
at 28 SwiftUI 0x000000011754055c $s14AttributeGraph0A0VyACyxGqd__c5ValueQyd__RszAA12StatefulRuleRd__lufcADSPyqd__GXEfU_ySv_So11AGAttributeatcyXEfU_ySv_AJtcqd__mcfu_ySv_AJtcfu0_TA + 26
at 29 AttributeGraph 0x0000000110585e9b _ZN2AG5Graph11UpdateStack6updateEv + 553
Exact solution:
emailUser
and passwordUser
in commonMain.Platformexpect var emailUser: String expect var passwordUser: String
actual var emailUser = "" actual var passwordUser = ""
actual var emailUser: String = AtomicReference("").value actual var passwordUser: String = AtomicReference("").value
You need to check out how kotlin-native concurrent-mutability works
In short, you can't use any var
in your shared code, that may be accessed from different threads. You had to wrap those values with Atomic
containers. Replace both emailUser
and passwordUser
with something like this:
private val emailUser = Atomic("")
private val passwordUser = Atomic("")
Also you can use delegated-properties so you don't need to write .value
each time
There's no Atomic declarations for common code, so you have to do you by yourself. In actual for iOS you can use native atomics and for android just make a simple wrapped.
Good news is that this won't stay for long, as JetBrains is planning to change concurrency model soon before KMP release. But for now we had to deal with it.