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)
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/README.md b/example/README.md
index 2aff3946..f3204eba 100644
--- a/example/README.md
+++ b/example/README.md
@@ -29,4 +29,14 @@ 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 devices
+flutter run --release -d Manoel --verbose
+# replace Manoel with your device's name/id
+```
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 3b7dcd39..172242af 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -2,6 +2,8 @@ plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
+ // uncomment to upload mapping files to PostHog
+ // 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 c248e574..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
@@ -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") {
+ 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
new file mode 100644
index 00000000..a4aceff1
--- /dev/null
+++ b/example/android/app/src/main/kotlin/com/example/flutter/NativeCrashHelper.kt
@@ -0,0 +1,24 @@
+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,
+ * allowing us to verify that symbolication (ProGuard mapping upload) works correctly.
+ */
+class NativeCrashHelper {
+ fun triggerCrash() {
+ // 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()
+ }
+}
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..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,9 +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 */; };
+ 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 */
@@ -45,7 +47,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 = ""; };
@@ -53,16 +54,19 @@
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 = ""; };
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 = ""; };
+ 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; };
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 */
@@ -80,6 +84,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
3144428D6DB02E557C4ECAF0 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -107,6 +112,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -136,7 +142,10 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ A1B2C3D4E5F60000AABBCCDD /* NativeCrashHelper.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ A809B0FA2F61A76C00637A80 /* Runner.app */,
+ A809B0FB2F61A76C00637A80 /* RunnerTests.xctest */,
);
path = Runner;
sourceTree = "";
@@ -173,7 +182,7 @@
);
name = RunnerTests;
productName = RunnerTests;
- productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
+ productReference = A809B0FB2F61A76C00637A80 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
@@ -187,15 +196,19 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 56F5972CE92C1CD5034EF702 /* [CP] Embed Pods Frameworks */,
+ 5C9C96BF1E7D401D439369FA /* [CP] Embed Pods Frameworks */,
+ A809B0FD2F61AD8000637A80 /* PostHog upload symbols */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
- productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productReference = A809B0FA2F61A76C00637A80 /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -227,6 +240,9 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 97C146EF1CF9000F007C117D /* Runner */;
projectDirPath = "";
projectRoot = "";
@@ -295,9 +311,9 @@
);
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";
};
- 56F5972CE92C1CD5034EF702 /* [CP] Embed Pods Frameworks */ = {
+ 5C9C96BF1E7D401D439369FA /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -305,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";
@@ -327,7 +347,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 = "# 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;
@@ -367,6 +406,7 @@
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ A1B2C3D4E5F60001AABBCCDD /* NativeCrashHelper.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -458,7 +498,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;
@@ -641,7 +681,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;
@@ -665,7 +705,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;
@@ -715,6 +755,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/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 @@
+
+
+
+
+
+
+
+
+
+
Bool {
- GeneratedPluginRegistrant.register(with: self)
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
+
+ func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
+ GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
+
+ let channel = FlutterMethodChannel(
+ name: "posthog_flutter_example",
+ binaryMessenger: engineBridge.applicationRegistrar.messenger()
+ )
+
+ channel.setMethodCallHandler { (call, result) in
+ if call.method == "triggerNativeCrash" {
+ NativeCrashHelper().triggerCrash()
+ } else {
+ result(FlutterMethodNotImplemented)
+ }
+ }
}
}
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 108a08eb..c6eadb92 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -55,5 +55,26 @@
com.posthog.posthog.AUTO_INIT
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneClassName
+ UIWindowScene
+ UISceneDelegateClassName
+ FlutterSceneDelegate
+ UISceneConfigurationName
+ flutter
+ UISceneStoryboardFile
+ Main
+
+
+
+
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/lib/main.dart b/example/lib/main.dart
index b089329e..ff9759f8 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 & Apple platforms)
if (kIsWeb) {
runZonedGuarded(
@@ -111,6 +114,7 @@ class InitialScreen extends StatefulWidget {
}
class InitialScreenState extends State {
+ static const _exampleChannel = MethodChannel('posthog_flutter_example');
final _posthogFlutterPlugin = Posthog();
dynamic _result = "";
@@ -164,8 +168,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: () {
@@ -490,6 +496,33 @@ 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
+ await _exampleChannel.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..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 */,
@@ -259,7 +262,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 1430;
+ LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
@@ -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;
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..4f14e498 100644
--- a/example/macos/Runner/AppDelegate.swift
+++ b/example/macos/Runner/AppDelegate.swift
@@ -1,9 +1,29 @@
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
+ }
+
+ 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" {
+ 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]
+ }
+}
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 {
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..f46462ba 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,27 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin {
}
#endif
+ // Configure error tracking
+ 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
+ }
+ #endif
+
+ // inApp configuration works across all platforms (including manual capture)
+ 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
+ }
+ }
+
// 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..865cab21 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,30 @@ 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 Apple platforms).
///
/// 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
+ /// **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.
+ /// 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.
+ /// 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;