Eirik Vale Aase
12/21/2021, 10:12 AMThrows(Throwable:class)
and we log these errors to Crashlytics. Problem is, the error we get (when we cast it as an NSError) has a user info dictionary with a KotlinException entry, and it has a code of 0 and a domain of “KotlinException”. This causes all errors logged to crashlytics to be grouped under the same bucket. The Kotlin throwable has a message which looks something like this (this is formatted for readability, it’s still a string)
Exception in http request:
Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline."
UserInfo={
_kCFStreamErrorCodeKey=50,
NSUnderlyingError=0x28035a940 {
Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)"
UserInfo={
_NSURLErrorNWPathKey=unsatisfied (No network route),
_kCFStreamErrorCodeKey=50,
_kCFStreamErrorDomainKey=1
}
},
_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <595E528B-8F59-4D85-88EE-DD084FC43CE7>.<1>,
_NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <595E528B-8F59-4D85-88EE-DD084FC43CE7>.<1>"
),
NSLocalizedDescription=The Internet connection appears to be offline.,
NSErrorFailingURLStringKey=SOME URL,
NSErrorFailingURLKey=SOME URL,
_kCFStreamErrorDomainKey=1
}
I could try and parse this string, extract regex matches etc, but I was wondering if there is a better way. It seems like Kotlin packs all of the interesting stuff and concatenates their string representations and just stuffs it into the userInfo entry. I would very much have liked to get the actual error instance.
How do people handle this?Rick Clephas
12/21/2021, 3:24 PMNSError
in the ObjCErrorException
◦ or by creating a NSError
with domain KotlinException
, code 0
and a KotlinException
entry in the user info
Looking at your example it looks like the NSError
contains a Kotlin Exception
which in turn “contains” a NSError
.
Could this be due to how a library is converting `NSError`s to `Exception`s?kpgalligan
12/21/2021, 4:13 PM@Throws
and then attempt to send handled exceptions to Crashlytics from Swift/Objc code? It's been a while, but I think we tried to make that work a while ago or something similar. Currently we convert Throwable
to a custom error report in Crashlytics directly, which allows you to get symbolicated Kotlin. Better grouping is kind of a TBD, TBH, as I believe Crashlytics handles that differently between hard and handled crashes: https://github.com/touchlab/Kermit/blob/main/kermit-crashlytics/src/darwinMain/kotlin/co/touchlab/kermit/crashlytics/CrashlyticsLogWriter.kt#L55@Throws
as that seems like less of an iOS thing (although I'm sure plenty would disagree). If a function expects to return an error, make that part of the function call, or let the call "crash". I think I've used @Throws
once in prod code, but pulled it out once we figured out what was actually happening.Rick Clephas
12/21/2021, 4:19 PMkpgalligan
12/21/2021, 4:20 PMOnce they are passed to Swift (and converted to NSErrors) it gets more complicated.Indeed, which is (to some degree) why I don't let them get there 🙂
konan::abort()
and a soft crash that shows the full stack. What I need to dig into more is how grouping happens and if we can improve that (just made a little note: https://github.com/touchlab/Kermit/issues/215).@Throws
method in quite a while, but I suspect it's very possible that there's a much easier/better method that I simply haven't come across yet. I'm emotionally prepared for somebody to be like, "why are you doing all that?! Just do ____", but haven't hit that yet.Rick Clephas
12/21/2021, 4:29 PMkpgalligan
12/21/2021, 4:30 PM@Throws
, which is a big assumption on my part)Rick Clephas
12/21/2021, 4:35 PMResult
object https://developer.apple.com/documentation/swift/result)kpgalligan
12/21/2021, 4:42 PM@Throws
wouldn't work there. Sync call as a result also makes logical sense to me vs throwing an exception for things you expect to happen. Either Swift's "Result" or something similar that comes out of Kotlin. If it were all Kotlin I'd say a sealed class, although going through Objc/Swift makes that's less expressive, obviously. In general, though, a sync call would return something that contains the error, if there is one. Unexpected exceptions would just cause crashes, unless it was a situation where you absolutely should never crash.@Throws
means if the app really should crash, it'll be up to the caller to figure that out, and I think that would be really difficult to do (especially if the caller is not a Kotlin/Native expert, which we can assume they're not). Encoding expected errors in the method response lets you put expected problems in the response, and also let the whole thing fall over if it's really in trouble, but again, situation dependent.Rick Clephas
12/21/2021, 4:54 PM@Throws
is a great start as it allows us to define which exceptions should be treated as non-fatal once. On the other hand making those non-fatal exceptions part of the return type feels more Swifty then Kotliny. I think ideally the @Throws
would make that particular function return a Result
or something similar in Swift without changing the Kotlin signature.kpgalligan
12/21/2021, 5:01 PM@Throws is a great start as it allows us to define which exceptions should be treated as non-fatal once.You mean as the annotation param? I guess I hadn't really used it that way. I guess you'd then need to define your own exception type hierarchy if you wanted to make sure the right types got sent. Like you'd want to catch exceptions from your networking code and wrap them in something along the lines of
KeepGoingException
so that anything unexpected that hit the Swift/Kotlin border would still cause a crash. Otherwise, it gets tricky to know what's an acceptable set of exceptions by type, or at least it can (for example, I don't know off hand what types Ktor throws. I was under the impression that that could differ by platform). Anyway, I'll have to think about the exception type parameter a bit. In theory, if you had a solid list of what's OK to handle, that would work, but in practice I don't think libraries/sdks are always precise that way (I can't tell you off hand exactly how sqliter or sqldelight throw for db calls, and I feel like I should know that 🙂 )Rick Clephas
12/21/2021, 5:06 PMSam
12/21/2021, 8:27 PM- (NSArray *)doSomethingErrorProneWithObject:(id)obj error:(NSError **)error
Swift muddies the waters a bit because it adds some syntactic sugar that makes it look similar to a Java exception. Swift would expose that method as the following
func doSomethingErrorProne(with obj: Any) throws
And calling it has to be in a do/try/catch
block.
do {
try doSomethingErrorProne(with: foo)
} catch {
// handle error
}
Rick Clephas
12/21/2021, 8:30 PMException
, Error
, RuntimeException
. Although I think this is more strict in Swift/ObjC than it is in Kotlin/Java.Sam
12/21/2021, 8:32 PMfatalError
function.Rick Clephas
02/12/2022, 4:23 PMNSError
as well so it should be possible to retrieve it but it’s not straightforward nor ideal. I have created a feature request to expose the interop to application/library code, but it seems this isn’t something we’ll be seeing in stdlib anytime soon. So in the meantime I have been experimenting with this a little and created a small library (haven’t published it just yet) that could potentially solve this. Would love to hear your feedback about the feature request and the current approach of the library 🙂. https://github.com/rickclephas/NSErrorKtkpgalligan
02/12/2022, 4:57 PMRick Clephas
02/12/2022, 5:06 PM