touchlab / CrashKiOS

Crash reporting for Kotlin/Native iOS applications
https://crashkios.touchlab.co/
Apache License 2.0
258 stars 15 forks source link

CrashKiOS Crashlytics Tutorial #39

Closed kpgalligan closed 1 year ago

kpgalligan commented 1 year ago

Overview

Set up Crashlytics for Android and iOS, then call from common code with CrashKiOS, and trigger catching crashes from iOS.

Basic outline

Start with a KMP project with Android and iOS, but no Crashlytics. Say fork KampKit

Sign up for Crashlyitcs for that project. We don't need detailed steps for this, as there's plenty of docs around that. Just "at the end, you should have google-services.json for Android and GoogleService-Info.plist for iOS.

Mention that it's generally suggested to hide then in .gitignore in open source projects.

Run and test that both of those work before trying to do KMP. If they don't work on their own, KMP won't fix it.

Make a note for iOS. If you run the app in a simulator from Xcode, it won't "crash". Xcode catches it. You need to run from Xcode, then "stop" it, then open the simulator and click on the app icon to run it directly there. Then it will "crash". Also, crash reports aren't sent until you start the app again. So, to test a "crash", you need to start the app with the icon, crash it, then restart it. If your simulated crash happens on app start, that won't work, so make sure your simulated crash is triggered by a user action (button click, etc).

Add the CrashKIOS kmp dependency to commonMain. Also, change the iOS Framework to isStatic = ture. We'll talk about dynamic later. Add kotlin.native.cacheKind.iosX64=none (and the other one for M1 which I don't have handy) to gradle.properties. Mention that we're working on an update to avoid this, but we need it for iOS linking issues.

Somewhere in startup code, you need to call enableCrashlytics() before using CrashKiOS. That's just part of the current design to avoid testing issues, although that may not be necessary in the future, depending on how we fix cacheKind above.

To test, send a handled exception from common code with CrashlyticsKotlin.sendHandledException(Exception("Some exception")). See if that comes through on both platforms.

For Android, we're done. For iOS, we need to catch unhandled exceptions that come from hard crashes. Somewhere in the startup code, call setCrashlyticsUnhandledExceptionHook(). To test, throw an uncaught exception from Kotlin code (again, triggered by user action). You should see that in the Crashlytics panel with Kotlin line numbers (AKA "symbolication").

If you're good with static frameworks, we're done (static is generally better, IMHO, but not everybody agrees). For dynamic, you need one more thing.

On iOS, we only add the definitions of Crashlytics during compile. The binary, the actual Crashlytics iOS library, isn't added until you build the iOS app. However, the Kotlin compiler expects to resolve everything when building the dynamic framework. You'll see this:

Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_FIRStackFrame", referenced from:
      objc-class-ref in result.o
  "_OBJC_CLASS_$_FIRExceptionModel", referenced from:
      objc-class-ref in result.o
  "_OBJC_CLASS_$_FIRCrashlytics", referenced from:
      objc-class-ref in result.o
  "_FIRCLSExceptionRecordNSException", referenced from:
      _co_touchlab_crashkios_crashlytics_FIRCLSExceptionRecordNSException_wrapper0 in result.o
ld: symbol(s) not found for architecture x86_64

It's saying it can't find the Crashlytics symbols we reference. We need to tell it "these symbols are OK. You'll find them later". To do that, you can add linker flags to Gradle config, which is kind of messy, or just use our Gradle plugin.


  id("co.touchlab.crashkios.crashlyticslink") version "0.8.1"
}```

Try that with our sample and confirm.

Then walk through `CrashlyticsKotlin.logMessage("Some message")` and `CrashlyticsKotlin.setCustomValue("someKey", "someValue")`