From 4fd063596178ec278844bcd036ce68d6064a8bf9 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 14:02:05 +0100 Subject: [PATCH 01/12] feat: add iOS native exception capture support --- .github/workflows/ci.yml | 2 +- .../com/example/flutter/MainActivity.kt | 18 ++++++++++- example/ios/Flutter/AppFrameworkInfo.plist | 2 -- example/ios/Runner.xcodeproj/project.pbxproj | 28 +++++++++++++++-- .../xcshareddata/xcschemes/Runner.xcscheme | 18 +++++++++++ example/ios/Runner/AppDelegate.swift | 21 ++++++++++++- example/ios/Runner/Info.plist | 21 +++++++++++++ example/lib/main.dart | 31 +++++++++++++++++++ .../macos/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 3 +- example/macos/Runner/AppDelegate.swift | 6 +++- .../darwin/posthog_flutter.podspec | 2 +- .../darwin/posthog_flutter/Package.swift | 2 +- .../PosthogFlutterPlugin.swift | 20 +++++++++++- posthog_flutter/lib/src/posthog_config.dart | 27 +++++++++++++--- 15 files changed, 185 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c419dfb..93e33157 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: - name: Enable Swift Package Manager if: matrix.package_manager == 'spm' - run: flutter config --enable-swift-package-manager + run: flutter config --enable-swift-package-manager # flutter config --no-enable-swift-package-manager - name: Install dependencies run: flutter pub get diff --git a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt index c248e574..d505aa18 100644 --- a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt @@ -1,5 +1,21 @@ package com.example.flutter import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel -class MainActivity : FlutterActivity() +class MainActivity : FlutterActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "posthog_flutter_example") + .setMethodCallHandler { call, result -> + if (call.method == "triggerNativeCrash") { + // Trigger a native crash by throwing an unhandled exception + throw RuntimeException("Test native crash from PostHog Flutter example") + } else { + result.notImplemented() + } + } + } +} diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf76..391a902b 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 22c59040..8cb5166b 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -53,6 +54,7 @@ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 74F175BA4BCEA03CB975D825 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 77D76B05F16B4B148310677D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7E9C12CB711543525381770A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -80,6 +82,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 3144428D6DB02E557C4ECAF0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -107,6 +110,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -187,13 +191,16 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 56F5972CE92C1CD5034EF702 /* [CP] Embed Pods Frameworks */, + 5C9C96BF1E7D401D439369FA /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -227,6 +234,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Runner */; projectDirPath = ""; projectRoot = ""; @@ -297,7 +307,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 56F5972CE92C1CD5034EF702 /* [CP] Embed Pods Frameworks */ = { + 5C9C96BF1E7D401D439369FA /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -715,6 +725,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d42..c3fedb29 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + com.posthog.posthog.AUTO_INIT + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/example/lib/main.dart b/example/lib/main.dart index b089329e..d22882d2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:posthog_flutter/posthog_flutter.dart'; import 'package:posthog_flutter_example/error_example.dart'; @@ -55,6 +56,8 @@ Future main() async { true; // Capture Dart runtime errors config.errorTrackingConfig.captureIsolateErrors = true; // Capture isolate errors + config.errorTrackingConfig.captureNativeExceptions = + true; // Capture native exceptions (Android & iOS) if (kIsWeb) { runZonedGuarded( @@ -490,6 +493,34 @@ class InitialScreenState extends State { }, child: const Text("Test Isolate Error Handler"), ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepOrange, + foregroundColor: Colors.white, + ), + onPressed: () async { + if (mounted && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Native crash triggered! The app will crash. ' + 'The exception will be sent on next launch.', + ), + backgroundColor: Colors.deepOrange, + duration: Duration(seconds: 3), + ), + ); + } + + // Give the snackbar time to show before crashing + await Future.delayed(const Duration(seconds: 1)); + + // Trigger a native crash via method channel + const channel = MethodChannel('posthog_flutter_example'); + await channel.invokeMethod('triggerNativeCrash'); + }, + child: const Text("Test Native Crash (will crash app!)"), + ), const Divider(), const Padding( padding: EdgeInsets.all(8.0), diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 9d2fde46..d4403927 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -259,7 +259,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 397f3d33..ac78810c 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef643..b3c17614 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/posthog_flutter/darwin/posthog_flutter.podspec b/posthog_flutter/darwin/posthog_flutter.podspec index e3c33711..2b27dc88 100644 --- a/posthog_flutter/darwin/posthog_flutter.podspec +++ b/posthog_flutter/darwin/posthog_flutter.podspec @@ -22,7 +22,7 @@ Postog flutter plugin s.osx.dependency 'FlutterMacOS' # ~> Version 3.40.0 up to, but not including, 4.0.0 - s.dependency 'PostHog', '>= 3.44.0', '< 4.0.0' + s.dependency 'PostHog', '>= 3.46.0', '< 4.0.0' s.ios.deployment_target = '13.0' # PH iOS SDK 3.0.0 requires >= 10.15 diff --git a/posthog_flutter/darwin/posthog_flutter/Package.swift b/posthog_flutter/darwin/posthog_flutter/Package.swift index e2a61d8c..90596f8b 100644 --- a/posthog_flutter/darwin/posthog_flutter/Package.swift +++ b/posthog_flutter/darwin/posthog_flutter/Package.swift @@ -14,7 +14,7 @@ let package = Package( ], dependencies: [ .package(name: "FlutterFramework", path: "../FlutterFramework"), - .package(url: "https://github.com/PostHog/posthog-ios", "3.44.0"..<"4.0.0") + .package(url: "https://github.com/PostHog/posthog-ios", "3.46.0"..<"4.0.0") ], targets: [ .target( diff --git a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift index 3d5a7682..b874b604 100644 --- a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift +++ b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift @@ -1,4 +1,4 @@ -import PostHog +@_spi(Experimental) import PostHog #if os(iOS) import Flutter import UIKit @@ -167,6 +167,24 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { } #endif + // Configure error tracking + #if os(iOS) || os(macOS) || os(tvOS) + if let errorConfig = posthogConfig["errorTrackingConfig"] as? [String: Any] { + if let captureNativeExceptions = errorConfig["captureNativeExceptions"] as? Bool { + config.errorTrackingConfig.autoCapture = captureNativeExceptions + } + if let inAppIncludes = errorConfig["inAppIncludes"] as? [String] { + config.errorTrackingConfig.inAppIncludes.append(contentsOf: inAppIncludes) + } + if let inAppExcludes = errorConfig["inAppExcludes"] as? [String] { + config.errorTrackingConfig.inAppExcludes.append(contentsOf: inAppExcludes) + } + if let inAppByDefault = errorConfig["inAppByDefault"] as? Bool { + config.errorTrackingConfig.inAppByDefault = inAppByDefault + } + } + #endif + // Update SDK name and version postHogSdkName = "posthog-flutter" postHogVersion = postHogFlutterVersion diff --git a/posthog_flutter/lib/src/posthog_config.dart b/posthog_flutter/lib/src/posthog_config.dart index 8492d118..1268b1f6 100644 --- a/posthog_flutter/lib/src/posthog_config.dart +++ b/posthog_flutter/lib/src/posthog_config.dart @@ -232,6 +232,7 @@ class PostHogErrorTrackingConfig { /// /// **Note:** /// - Flutter web: Not supported + /// - Android: Not supported /// final inAppExcludes = []; @@ -248,6 +249,7 @@ class PostHogErrorTrackingConfig { /// /// **Note:** /// - Flutter web: Not supported + /// - Android: Not supported /// var inAppByDefault = true; @@ -275,14 +277,29 @@ class PostHogErrorTrackingConfig { /// Default: false var capturePlatformDispatcherErrors = false; - /// Enable automatic capture of exceptions in the native SDKs (Android only for now) + /// Enable automatic capture of exceptions in the native SDKs (Android and iOS). /// /// Controls whether native exceptions are captured. /// - /// **Note:** - /// - iOS: Not supported - /// - Android: Java/Kotlin exceptions only (no native C/C++ crashes) - /// - Android: No stacktrace demangling for minified builds + /// **iOS:** + /// + /// Captures Mach exceptions (e.g., EXC_BAD_ACCESS), POSIX signals + /// (e.g., SIGSEGV, SIGABRT), and uncaught NSExceptions. + /// Crashes are persisted to disk and sent as `$exception` events with + /// level "fatal" on the next app launch. + /// Available on iOS, macOS, and tvOS only (not watchOS or visionOS). + /// + /// For symbolicated stack traces, add a build phase script to your + /// Xcode project to upload debug symbols. + /// See: https://posthog.com/docs/error-tracking/upload-source-maps/ios + /// + /// **Android:** + /// + /// Captures Java/Kotlin exceptions only (no native C/C++ crashes). + /// + /// Stacktrace demangling for minified builds is supported by installing + /// the PostHog Gradle plugin to upload ProGuard/R8 mappings. + /// See: https://posthog.com/docs/error-tracking/upload-mappings/android /// /// Default: false var captureNativeExceptions = false; From d60819a9339e2042667c01310533b10881ea72ea Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 14:25:17 +0100 Subject: [PATCH 02/12] fix example --- example/ios/Runner/AppDelegate.swift | 7 ------- example/macos/Runner/AppDelegate.swift | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 0df9ba47..aa11e92b 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -3,13 +3,6 @@ import Flutter @main @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index b3c17614..219290dd 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -10,4 +10,22 @@ class AppDelegate: FlutterAppDelegate { override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } + + override func applicationDidFinishLaunching(_ notification: Notification) { + let controller = mainFlutterWindow?.contentViewController as! FlutterViewController + let channel = FlutterMethodChannel( + name: "posthog_flutter_example", + binaryMessenger: controller.engine.binaryMessenger + ) + + channel.setMethodCallHandler { (call, result) in + if call.method == "triggerNativeCrash" { + // Trigger a native crash by index out of range + let array: [Int] = [] + _ = array[99] + } else { + result(FlutterMethodNotImplemented) + } + } + } } From 3d459dfa8d6302e12c026f98475e8f9213af7c17 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 15:16:16 +0100 Subject: [PATCH 03/12] ios symbols --- example/README.md | 11 +++++- example/ios/Runner.xcodeproj/project.pbxproj | 40 +++++++++++++++----- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/example/README.md b/example/README.md index 2aff3946..173898f6 100644 --- a/example/README.md +++ b/example/README.md @@ -29,4 +29,13 @@ posthog-cli sourcemap upload --directory build/web cd build/web # https://pub.dev/packages/dhttpd dhttpd -``` \ No newline at end of file +``` + +## Running Apple example + +``` +# needs device pairing - enable debug mode on the device +# make sure you have a valid team in Xcode, go to Signing & Capabilities +flutter run --release -d Manoel --verbose +# replace Manoel with your device's name +``` diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 8cb5166b..58d56ccc 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -46,7 +46,6 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 552A4626DB9955B4837A84BA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 72D3235DB526308040CCA83E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; @@ -59,12 +58,13 @@ 7E9C12CB711543525381770A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A5B2412C165B920EC346A024 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + A809B0FA2F61A76C00637A80 /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A809B0FB2F61A76C00637A80 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; BD641B49EA0880C84FE2BD75 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BDC5BF357116FAB133D0276D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -141,6 +141,8 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + A809B0FA2F61A76C00637A80 /* Runner.app */, + A809B0FB2F61A76C00637A80 /* RunnerTests.xctest */, ); path = Runner; sourceTree = ""; @@ -177,7 +179,7 @@ ); name = RunnerTests; productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productReference = A809B0FB2F61A76C00637A80 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { @@ -192,6 +194,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 5C9C96BF1E7D401D439369FA /* [CP] Embed Pods Frameworks */, + A809B0FD2F61AD8000637A80 /* PostHog upload symbols */, ); buildRules = ( ); @@ -202,7 +205,7 @@ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, ); productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productReference = A809B0FA2F61A76C00637A80 /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -305,7 +308,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; 5C9C96BF1E7D401D439369FA /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -337,7 +340,26 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; + A809B0FD2F61AD8000637A80 /* PostHog upload symbols */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "PostHog upload symbols"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "POSTHOG_INCLUDE_SOURCE=1 ${PODS_ROOT}/PostHog/build-tools/upload-symbols.sh\n"; }; E0EB70FF4694C64F4A64348B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -468,7 +490,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 8WKF8J5LV3; + DEVELOPMENT_TEAM = PNC2XCH2XP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -651,7 +673,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 8WKF8J5LV3; + DEVELOPMENT_TEAM = PNC2XCH2XP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -675,7 +697,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 8WKF8J5LV3; + DEVELOPMENT_TEAM = PNC2XCH2XP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; From 188dc703f3eb9b44e0ecde22bbceb5e8f3ec076d Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 15:58:05 +0100 Subject: [PATCH 04/12] fix --- example/README.md | 3 ++- example/android/app/build.gradle | 1 + .../src/main/kotlin/com/example/flutter/MainActivity.kt | 9 +++++++-- example/lib/main.dart | 6 ++++-- posthog_flutter/android/build.gradle | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/example/README.md b/example/README.md index 173898f6..f3204eba 100644 --- a/example/README.md +++ b/example/README.md @@ -36,6 +36,7 @@ dhttpd ``` # needs device pairing - enable debug mode on the device # make sure you have a valid team in Xcode, go to Signing & Capabilities +flutter devices flutter run --release -d Manoel --verbose -# replace Manoel with your device's name +# replace Manoel with your device's name/id ``` diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 3b7dcd39..8e820b29 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -2,6 +2,7 @@ plugins { id "com.android.application" id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" + id "com.posthog.android" version "1.0.3" } def localProperties = new Properties() diff --git a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt index d505aa18..3cad612d 100644 --- a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt @@ -11,8 +11,13 @@ class MainActivity : FlutterActivity() { MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "posthog_flutter_example") .setMethodCallHandler { call, result -> if (call.method == "triggerNativeCrash") { - // Trigger a native crash by throwing an unhandled exception - throw RuntimeException("Test native crash from PostHog Flutter example") + // Crash on a background thread because Flutter wraps and + // swallows exceptions from the method channel handler as + // a PlatformException, preventing the app from actually crashing. + Thread { + throw RuntimeException("Test native crash from PostHog Flutter example") + }.start() + result.success(null) } else { result.notImplemented() } diff --git a/example/lib/main.dart b/example/lib/main.dart index d22882d2..0e2b6530 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -167,8 +167,10 @@ class InitialScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold), ), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + Wrap( + alignment: WrapAlignment.spaceEvenly, + spacing: 8.0, + runSpacing: 8.0, children: [ ElevatedButton( onPressed: () { diff --git a/posthog_flutter/android/build.gradle b/posthog_flutter/android/build.gradle index 049338ac..9665b754 100644 --- a/posthog_flutter/android/build.gradle +++ b/posthog_flutter/android/build.gradle @@ -54,7 +54,7 @@ android { testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation 'org.mockito:mockito-core:5.0.0' // + Version 3.31.0 and the versions up to 4.0.0, not including 4.0.0 and higher - implementation 'com.posthog:posthog-android:[3.35.0,4.0.0]' + implementation 'com.posthog:posthog-android:[3.37.0,4.0.0]' } testOptions { From 22a9f5867aff9bb5a74583013194ae07331d3512 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 16:07:59 +0100 Subject: [PATCH 05/12] chore: address review feedback --- example/lib/main.dart | 6 ++--- .../PosthogFlutterPlugin.swift | 27 ++++++++++--------- posthog_flutter/lib/src/posthog_config.dart | 7 ++--- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e2b6530..ff9759f8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -57,7 +57,7 @@ Future main() async { config.errorTrackingConfig.captureIsolateErrors = true; // Capture isolate errors config.errorTrackingConfig.captureNativeExceptions = - true; // Capture native exceptions (Android & iOS) + true; // Capture native exceptions (Android & Apple platforms) if (kIsWeb) { runZonedGuarded( @@ -114,6 +114,7 @@ class InitialScreen extends StatefulWidget { } class InitialScreenState extends State { + static const _exampleChannel = MethodChannel('posthog_flutter_example'); final _posthogFlutterPlugin = Posthog(); dynamic _result = ""; @@ -518,8 +519,7 @@ class InitialScreenState extends State { await Future.delayed(const Duration(seconds: 1)); // Trigger a native crash via method channel - const channel = MethodChannel('posthog_flutter_example'); - await channel.invokeMethod('triggerNativeCrash'); + await _exampleChannel.invokeMethod('triggerNativeCrash'); }, child: const Text("Test Native Crash (will crash app!)"), ), diff --git a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift index b874b604..f46462ba 100644 --- a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift +++ b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift @@ -168,22 +168,25 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { #endif // Configure error tracking - #if os(iOS) || os(macOS) || os(tvOS) - if let errorConfig = posthogConfig["errorTrackingConfig"] as? [String: Any] { + if let errorConfig = posthogConfig["errorTrackingConfig"] as? [String: Any] { + // autoCapture is only available on iOS, macOS, and tvOS + #if os(iOS) || os(macOS) || os(tvOS) if let captureNativeExceptions = errorConfig["captureNativeExceptions"] as? Bool { config.errorTrackingConfig.autoCapture = captureNativeExceptions } - if let inAppIncludes = errorConfig["inAppIncludes"] as? [String] { - config.errorTrackingConfig.inAppIncludes.append(contentsOf: inAppIncludes) - } - if let inAppExcludes = errorConfig["inAppExcludes"] as? [String] { - config.errorTrackingConfig.inAppExcludes.append(contentsOf: inAppExcludes) - } - if let inAppByDefault = errorConfig["inAppByDefault"] as? Bool { - config.errorTrackingConfig.inAppByDefault = inAppByDefault - } + #endif + + // inApp configuration works across all platforms (including manual capture) + if let inAppIncludes = errorConfig["inAppIncludes"] as? [String] { + config.errorTrackingConfig.inAppIncludes.append(contentsOf: inAppIncludes) } - #endif + if let inAppExcludes = errorConfig["inAppExcludes"] as? [String] { + config.errorTrackingConfig.inAppExcludes.append(contentsOf: inAppExcludes) + } + if let inAppByDefault = errorConfig["inAppByDefault"] as? Bool { + config.errorTrackingConfig.inAppByDefault = inAppByDefault + } + } // Update SDK name and version postHogSdkName = "posthog-flutter" diff --git a/posthog_flutter/lib/src/posthog_config.dart b/posthog_flutter/lib/src/posthog_config.dart index 1268b1f6..865cab21 100644 --- a/posthog_flutter/lib/src/posthog_config.dart +++ b/posthog_flutter/lib/src/posthog_config.dart @@ -277,17 +277,18 @@ class PostHogErrorTrackingConfig { /// Default: false var capturePlatformDispatcherErrors = false; - /// Enable automatic capture of exceptions in the native SDKs (Android and iOS). + /// Enable automatic capture of exceptions in the native SDKs + /// (Android and Apple platforms). /// /// Controls whether native exceptions are captured. /// - /// **iOS:** + /// **Apple (iOS, macOS, tvOS):** /// /// Captures Mach exceptions (e.g., EXC_BAD_ACCESS), POSIX signals /// (e.g., SIGSEGV, SIGABRT), and uncaught NSExceptions. /// Crashes are persisted to disk and sent as `$exception` events with /// level "fatal" on the next app launch. - /// Available on iOS, macOS, and tvOS only (not watchOS or visionOS). + /// Not available on watchOS or visionOS due to platform limitations. /// /// For symbolicated stack traces, add a build phase script to your /// Xcode project to upload debug symbols. From c73ff749b923c4a3914b549ee98a895e7f6a1c26 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 16:08:59 +0100 Subject: [PATCH 06/12] chore: add changeset entry --- .changeset/bright-dogs-fly.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/bright-dogs-fly.md diff --git a/.changeset/bright-dogs-fly.md b/.changeset/bright-dogs-fly.md new file mode 100644 index 00000000..268416bb --- /dev/null +++ b/.changeset/bright-dogs-fly.md @@ -0,0 +1,5 @@ +--- +'posthog_flutter': minor +--- + +Add native exception capture support for Apple platforms (iOS, macOS, tvOS) From 11ef2a1fe9384be3f6268e685fe28f4f7586bceb Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 16:13:11 +0100 Subject: [PATCH 07/12] chore: extract native crash to separate class for minification testing --- .../main/kotlin/com/example/flutter/MainActivity.kt | 2 +- .../kotlin/com/example/flutter/NativeCrashHelper.kt | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt diff --git a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt index 3cad612d..2a15c2d8 100644 --- a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt @@ -15,7 +15,7 @@ class MainActivity : FlutterActivity() { // swallows exceptions from the method channel handler as // a PlatformException, preventing the app from actually crashing. Thread { - throw RuntimeException("Test native crash from PostHog Flutter example") + NativeCrashHelper().triggerCrash() }.start() result.success(null) } else { diff --git a/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt b/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt new file mode 100644 index 00000000..0a8d01c9 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt @@ -0,0 +1,12 @@ +package com.example.flutter + +/** + * Helper class to trigger a native crash for testing error tracking. + * This class and its methods will be minified by R8/ProGuard in release builds, + * allowing us to verify that symbolication (ProGuard mapping upload) works correctly. + */ +class NativeCrashHelper { + fun triggerCrash() { + throw RuntimeException("Test native crash from PostHog Flutter example") + } +} From d70b807f3e99354561bba25957b20a0196fe99a2 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 16:13:21 +0100 Subject: [PATCH 08/12] chore: use custom exception type for native crash testing --- .../main/kotlin/com/example/flutter/NativeCrashHelper.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt b/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt index 0a8d01c9..103ae21f 100644 --- a/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt +++ b/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt @@ -1,5 +1,12 @@ package com.example.flutter +/** + * Custom exception type for testing error tracking symbolication. + * This class will be minified by R8/ProGuard in release builds, + * allowing us to verify that symbolication (ProGuard mapping upload) works correctly. + */ +class PostHogExampleException(message: String) : Exception(message) + /** * Helper class to trigger a native crash for testing error tracking. * This class and its methods will be minified by R8/ProGuard in release builds, @@ -7,6 +14,6 @@ package com.example.flutter */ class NativeCrashHelper { fun triggerCrash() { - throw RuntimeException("Test native crash from PostHog Flutter example") + throw PostHogExampleException("Test native crash from PostHog Flutter example") } } From 0a4906fd90a7c453a0cd6600cf103c6ee1f6ad71 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 16:13:54 +0100 Subject: [PATCH 09/12] chore: move thread to triggerCrash method --- .../src/main/kotlin/com/example/flutter/MainActivity.kt | 7 +------ .../main/kotlin/com/example/flutter/NativeCrashHelper.kt | 7 ++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt index 2a15c2d8..2dcf69bd 100644 --- a/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/flutter/MainActivity.kt @@ -11,12 +11,7 @@ class MainActivity : FlutterActivity() { MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "posthog_flutter_example") .setMethodCallHandler { call, result -> if (call.method == "triggerNativeCrash") { - // Crash on a background thread because Flutter wraps and - // swallows exceptions from the method channel handler as - // a PlatformException, preventing the app from actually crashing. - Thread { - NativeCrashHelper().triggerCrash() - }.start() + NativeCrashHelper().triggerCrash() result.success(null) } else { result.notImplemented() diff --git a/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt b/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt index 103ae21f..a4aceff1 100644 --- a/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt +++ b/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt @@ -14,6 +14,11 @@ class PostHogExampleException(message: String) : Exception(message) */ class NativeCrashHelper { fun triggerCrash() { - throw PostHogExampleException("Test native crash from PostHog Flutter example") + // Crash on a background thread because Flutter wraps and + // swallows exceptions from the method channel handler as + // a PlatformException, preventing the app from actually crashing. + Thread { + throw PostHogExampleException("Test native crash from PostHog Flutter example") + }.start() } } From 6f93647ab7d963db89efb0527efb5b45f9c81546 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 16:18:12 +0100 Subject: [PATCH 10/12] chore: extract native crash to NativeCrashHelper class for iOS and macOS --- example/ios/Runner/AppDelegate.swift | 4 +--- example/ios/Runner/NativeCrashHelper.swift | 11 +++++++++++ example/macos/Runner/AppDelegate.swift | 4 +--- example/macos/Runner/NativeCrashHelper.swift | 11 +++++++++++ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 example/ios/Runner/NativeCrashHelper.swift create mode 100644 example/macos/Runner/NativeCrashHelper.swift diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index aa11e92b..90da8694 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -14,9 +14,7 @@ import Flutter channel.setMethodCallHandler { (call, result) in if call.method == "triggerNativeCrash" { - // Trigger a native crash by index out of range - let array: [Int] = [] - _ = array[99] + NativeCrashHelper().triggerCrash() } else { result(FlutterMethodNotImplemented) } diff --git a/example/ios/Runner/NativeCrashHelper.swift b/example/ios/Runner/NativeCrashHelper.swift new file mode 100644 index 00000000..39b76d02 --- /dev/null +++ b/example/ios/Runner/NativeCrashHelper.swift @@ -0,0 +1,11 @@ +import Foundation + +/// Helper class to trigger a native crash for testing error tracking. +/// Having a dedicated class and method produces a clearer stack trace +/// for verifying symbolication works correctly. +class NativeCrashHelper { + func triggerCrash() { + let array: [Int] = [] + _ = array[99] + } +} diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index 219290dd..4f14e498 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -20,9 +20,7 @@ class AppDelegate: FlutterAppDelegate { channel.setMethodCallHandler { (call, result) in if call.method == "triggerNativeCrash" { - // Trigger a native crash by index out of range - let array: [Int] = [] - _ = array[99] + NativeCrashHelper().triggerCrash() } else { result(FlutterMethodNotImplemented) } diff --git a/example/macos/Runner/NativeCrashHelper.swift b/example/macos/Runner/NativeCrashHelper.swift new file mode 100644 index 00000000..39b76d02 --- /dev/null +++ b/example/macos/Runner/NativeCrashHelper.swift @@ -0,0 +1,11 @@ +import Foundation + +/// Helper class to trigger a native crash for testing error tracking. +/// Having a dedicated class and method produces a clearer stack trace +/// for verifying symbolication works correctly. +class NativeCrashHelper { + func triggerCrash() { + let array: [Int] = [] + _ = array[99] + } +} From d37c9d389452b8c04634617864196333fa5a2011 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 16:20:20 +0100 Subject: [PATCH 11/12] chore: add NativeCrashHelper.swift to Xcode projects --- example/ios/Runner.xcodeproj/project.pbxproj | 4 ++++ example/macos/Runner.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 58d56ccc..c37b1956 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + A1B2C3D4E5F60001AABBCCDD /* NativeCrashHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -51,6 +52,7 @@ 72D3235DB526308040CCA83E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCrashHelper.swift; sourceTree = ""; }; 74F175BA4BCEA03CB975D825 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 77D76B05F16B4B148310677D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; @@ -140,6 +142,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, A809B0FA2F61A76C00637A80 /* Runner.app */, A809B0FB2F61A76C00637A80 /* RunnerTests.xctest */, @@ -399,6 +402,7 @@ buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + A1B2C3D4E5F60001AABBCCDD /* NativeCrashHelper.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index d4403927..d34cf817 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + B1C2D3E4F5A60001DDEEFFAA /* NativeCrashHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C2D3E4F5A60000DDEEFFAA /* NativeCrashHelper.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; @@ -68,6 +69,7 @@ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + B1C2D3E4F5A60000DDEEFFAA /* NativeCrashHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCrashHelper.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; @@ -190,6 +192,7 @@ isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + B1C2D3E4F5A60000DDEEFFAA /* NativeCrashHelper.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, @@ -438,6 +441,7 @@ files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + B1C2D3E4F5A60001DDEEFFAA /* NativeCrashHelper.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; From 8d1eb160a8622379db41adedf72156bcaae65e1f Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 11 Mar 2026 18:00:42 +0100 Subject: [PATCH 12/12] fix --- example/android/app/build.gradle | 3 ++- example/ios/Runner.xcodeproj/project.pbxproj | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 8e820b29..172242af 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -2,7 +2,8 @@ plugins { id "com.android.application" id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" - id "com.posthog.android" version "1.0.3" + // uncomment to upload mapping files to PostHog + // id "com.posthog.android" version "1.0.3" } def localProperties = new Properties() diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c37b1956..4f4f1081 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -12,11 +12,11 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - A1B2C3D4E5F60001AABBCCDD /* NativeCrashHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + A1B2C3D4E5F60001AABBCCDD /* NativeCrashHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */; }; BDF5FF89BC2B236EF0B81DD2 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDC5BF357116FAB133D0276D /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ @@ -52,7 +52,6 @@ 72D3235DB526308040CCA83E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCrashHelper.swift; sourceTree = ""; }; 74F175BA4BCEA03CB975D825 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 77D76B05F16B4B148310677D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; @@ -64,6 +63,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCrashHelper.swift; sourceTree = ""; }; A5B2412C165B920EC346A024 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; A809B0FA2F61A76C00637A80 /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; A809B0FB2F61A76C00637A80 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -241,7 +241,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Runner */; projectDirPath = ""; @@ -321,10 +321,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -362,7 +366,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "POSTHOG_INCLUDE_SOURCE=1 ${PODS_ROOT}/PostHog/build-tools/upload-symbols.sh\n"; + shellScript = "# uncomment to upload mapping files to PostHog\n# POSTHOG_INCLUDE_SOURCE=1 ${PODS_ROOT}/PostHog/build-tools/upload-symbols.sh\n"; }; E0EB70FF4694C64F4A64348B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -753,7 +757,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; };