From da957dba5d95ddc59535808df27748b73982f4f9 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Sep 2026 15:16:07 +0200 Subject: [PATCH 1/5] fix: preserve chained error handlers across restarts --- .changeset/quiet-handlers-forward.md | 5 + ...rror_tracking_autocapture_integration.dart | 59 +++--- ...error_tracking_handler_lifecycle_test.dart | 191 ++++++++++++++++++ 3 files changed, 230 insertions(+), 25 deletions(-) create mode 100644 .changeset/quiet-handlers-forward.md create mode 100644 posthog_flutter/test/posthog_error_tracking_handler_lifecycle_test.dart diff --git a/.changeset/quiet-handlers-forward.md b/.changeset/quiet-handlers-forward.md new file mode 100644 index 00000000..af7d4cd5 --- /dev/null +++ b/.changeset/quiet-handlers-forward.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": patch +--- + +Preserve chained error handlers after teardown and prevent duplicate exception captures when error tracking restarts. diff --git a/posthog_flutter/lib/src/error_tracking/posthog_error_tracking_autocapture_integration.dart b/posthog_flutter/lib/src/error_tracking/posthog_error_tracking_autocapture_integration.dart index fa5d826d..037566b9 100644 --- a/posthog_flutter/lib/src/error_tracking/posthog_error_tracking_autocapture_integration.dart +++ b/posthog_flutter/lib/src/error_tracking/posthog_error_tracking_autocapture_integration.dart @@ -24,6 +24,8 @@ class PostHogErrorTrackingAutoCaptureIntegration { // Store original handlers (we'll chain with them from our handler) FlutterExceptionHandler? _originalFlutterErrorHandler; ErrorCallback? _originalPlatformErrorHandler; + FlutterExceptionHandler? _flutterErrorHandler; + ErrorCallback? _platformErrorHandler; // Isolate error handling final IsolateErrorHandler _isolateErrorHandler = IsolateErrorHandler(); @@ -103,10 +105,12 @@ class PostHogErrorTrackingAutoCaptureIntegration { _isEnabled = false; // Restore original handlers only if our own handler is still set - if (FlutterError.onError == _posthogFlutterErrorHandler) { + if (_flutterErrorHandler != null && + FlutterError.onError == _flutterErrorHandler) { FlutterError.onError = _originalFlutterErrorHandler; } - if (PlatformDispatcher.instance.onError == _posthogPlatformErrorHandler) { + if (_platformErrorHandler != null && + PlatformDispatcher.instance.onError == _platformErrorHandler) { PlatformDispatcher.instance.onError = _originalPlatformErrorHandler; } @@ -116,18 +120,25 @@ class PostHogErrorTrackingAutoCaptureIntegration { // release refs _originalFlutterErrorHandler = null; _originalPlatformErrorHandler = null; + _flutterErrorHandler = null; + _platformErrorHandler = null; } /// Flutter framework error handler void _setupFlutterErrorHandler() { - // prevent circular calls - if (FlutterError.onError == _posthogFlutterErrorHandler) { - return; - } - - _originalFlutterErrorHandler = FlutterError.onError; - - FlutterError.onError = _posthogFlutterErrorHandler; + final originalHandler = FlutterError.onError; + _originalFlutterErrorHandler = originalHandler; + + // Retained wrappers must keep their own delegate and stay inactive on restart. + late final FlutterExceptionHandler handler; + handler = (details) { + if (_isEnabled && identical(_flutterErrorHandler, handler)) { + _posthogFlutterErrorHandler(details); + } + originalHandler?.call(details); + }; + _flutterErrorHandler = handler; + FlutterError.onError = handler; } void _posthogFlutterErrorHandler(FlutterErrorDetails details) { @@ -167,9 +178,6 @@ class PostHogErrorTrackingAutoCaptureIntegration { "Error not captured because FlutterErrorDetails.silent is true and captureSilentFlutterErrors is false", ); } - - // Call the original handler, if any - _originalFlutterErrorHandler?.call(details); } /// Platform error handler for Dart runtime errors @@ -180,16 +188,21 @@ class PostHogErrorTrackingAutoCaptureIntegration { return; } - // prevent circular calls - if (PlatformDispatcher.instance.onError == _posthogPlatformErrorHandler) { - return; - } - - _originalPlatformErrorHandler = PlatformDispatcher.instance.onError; - PlatformDispatcher.instance.onError = _posthogPlatformErrorHandler; + final originalHandler = PlatformDispatcher.instance.onError; + _originalPlatformErrorHandler = originalHandler; + + late final ErrorCallback handler; + handler = (error, stackTrace) { + if (_isEnabled && identical(_platformErrorHandler, handler)) { + _posthogPlatformErrorHandler(error, stackTrace); + } + return originalHandler?.call(error, stackTrace) ?? false; + }; + _platformErrorHandler = handler; + PlatformDispatcher.instance.onError = handler; } - bool _posthogPlatformErrorHandler(Object error, StackTrace stackTrace) { + void _posthogPlatformErrorHandler(Object error, StackTrace stackTrace) { final wrappedError = PostHogException( source: error, mechanism: 'PlatformDispatcher', @@ -197,10 +210,6 @@ class PostHogErrorTrackingAutoCaptureIntegration { ); _captureException(error: wrappedError, stackTrace: stackTrace); - - // Call the original handler, if any - // False otherwise, so that default fallback mechanism is used - return _originalPlatformErrorHandler?.call(error, stackTrace) ?? false; } /// Isolate error handler for current isolate errors diff --git a/posthog_flutter/test/posthog_error_tracking_handler_lifecycle_test.dart b/posthog_flutter/test/posthog_error_tracking_handler_lifecycle_test.dart new file mode 100644 index 00000000..8971d01e --- /dev/null +++ b/posthog_flutter/test/posthog_error_tracking_handler_lifecycle_test.dart @@ -0,0 +1,191 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:posthog_flutter/src/error_tracking/posthog_error_tracking_autocapture_integration.dart'; +import 'package:posthog_flutter/src/posthog_config.dart'; + +import 'posthog_flutter_platform_interface_fake.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + for (final platformHandler in [false, true]) { + group( + platformHandler ? 'PlatformDispatcher.onError' : 'FlutterError.onError', + () { + late FlutterExceptionHandler? savedFlutterHandler; + late ErrorCallback? savedPlatformHandler; + late PostHogErrorTrackingConfig config; + late PosthogFlutterPlatformFake posthog; + late PostHogErrorTrackingAutoCaptureIntegration integration; + late bool Function() dispatch; + var upstreamCalls = 0; + var wrapperCalls = 0; + + setUp(() { + savedFlutterHandler = FlutterError.onError; + savedPlatformHandler = PlatformDispatcher.instance.onError; + upstreamCalls = 0; + wrapperCalls = 0; + posthog = PosthogFlutterPlatformFake(); + config = PostHogErrorTrackingConfig() + ..captureFlutterErrors = !platformHandler + ..capturePlatformDispatcherErrors = platformHandler; + + if (platformHandler) { + PlatformDispatcher.instance.onError = (error, stack) { + upstreamCalls++; + return true; + }; + } else { + FlutterError.onError = (details) => upstreamCalls++; + } + integration = PostHogErrorTrackingAutoCaptureIntegration.install( + config: config, + posthog: posthog, + )!; + + if (platformHandler) { + final previous = PlatformDispatcher.instance.onError!; + PlatformDispatcher.instance.onError = (error, stack) { + if (++wrapperCalls > 1) { + throw StateError('handler chain recursed'); + } + return previous(error, stack); + }; + dispatch = () => PlatformDispatcher.instance.onError!( + StateError('handler lifecycle repro'), + StackTrace.current, + ); + } else { + final previous = FlutterError.onError!; + FlutterError.onError = (details) { + if (++wrapperCalls > 1) { + throw StateError('handler chain recursed'); + } + previous(details); + }; + dispatch = () { + FlutterError.onError!(FlutterErrorDetails( + exception: StateError('handler lifecycle repro'), + stack: StackTrace.current, + )); + return true; + }; + } + }); + + tearDown(() { + PostHogErrorTrackingAutoCaptureIntegration.uninstall(); + FlutterError.onError = savedFlutterHandler; + PlatformDispatcher.instance.onError = savedPlatformHandler; + }); + + test('installed handler captures once and preserves the upstream chain', + () { + final handled = dispatch(); + + expect(posthog.capturedExceptions, hasLength(1)); + expect(wrapperCalls, 1); + expect(upstreamCalls, 1); + expect(handled, isTrue); + }); + + test('uninstall restores a null upstream handler', () { + PostHogErrorTrackingAutoCaptureIntegration.uninstall(); + if (platformHandler) { + PlatformDispatcher.instance.onError = null; + } else { + FlutterError.onError = null; + } + PostHogErrorTrackingAutoCaptureIntegration.install( + config: config, + posthog: posthog, + ); + + final handled = dispatch(); + expect(posthog.capturedExceptions, hasLength(1)); + if (platformHandler) expect(handled, isFalse); + + PostHogErrorTrackingAutoCaptureIntegration.uninstall(); + + expect( + platformHandler + ? PlatformDispatcher.instance.onError + : FlutterError.onError, + isNull, + ); + }); + + test('uninstall preserves the upstream handler chain', () { + PostHogErrorTrackingAutoCaptureIntegration.uninstall(); + + final handled = dispatch(); + + expect(wrapperCalls, 1); + expect(upstreamCalls, 1); + expect(handled, isTrue); + }); + + test('uninstall stops captures from a retained handler', () { + PostHogErrorTrackingAutoCaptureIntegration.uninstall(); + + dispatch(); + + expect(posthog.capturedExceptions, isEmpty); + }); + + test('restart captures once without a circular handler chain', () { + integration.stop(); + integration.start(); + + final handled = dispatch(); + + expect(posthog.capturedExceptions, hasLength(1)); + expect(wrapperCalls, 1); + expect(upstreamCalls, 1); + expect(handled, isTrue); + }); + + test('repeated reinstalls leave only the current integration capturing', + () { + final retiredPlatforms = [posthog]; + for (var i = 0; i < 3; i++) { + PostHogErrorTrackingAutoCaptureIntegration.uninstall(); + posthog = PosthogFlutterPlatformFake(); + PostHogErrorTrackingAutoCaptureIntegration.install( + config: config, + posthog: posthog, + ); + if (i < 2) retiredPlatforms.add(posthog); + } + + final handled = dispatch(); + + expect(posthog.capturedExceptions, hasLength(1)); + for (final retired in retiredPlatforms) { + expect(retired.capturedExceptions, isEmpty); + } + expect(wrapperCalls, 1); + expect(upstreamCalls, 1); + expect(handled, isTrue); + }); + + test('reinstall captures once and preserves the upstream chain', () { + PostHogErrorTrackingAutoCaptureIntegration.uninstall(); + PostHogErrorTrackingAutoCaptureIntegration.install( + config: config, + posthog: posthog, + ); + + final handled = dispatch(); + + expect(posthog.capturedExceptions, hasLength(1)); + expect(wrapperCalls, 1); + expect(upstreamCalls, 1); + expect(handled, isTrue); + }); + }); + } +} From 5517b70f71127c9bfcfffebcecc5a6d7a0adb6e8 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Sep 2026 15:33:30 +0200 Subject: [PATCH 2/5] fix: reapply automatic error hooks on repeated setup --- .changeset/fresh-error-hooks-sync.md | 5 + posthog_flutter/lib/src/posthog.dart | 6 +- .../test/repeated_setup_hooks_test.dart | 209 ++++++++++++++++++ 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 .changeset/fresh-error-hooks-sync.md create mode 100644 posthog_flutter/test/repeated_setup_hooks_test.dart diff --git a/.changeset/fresh-error-hooks-sync.md b/.changeset/fresh-error-hooks-sync.md new file mode 100644 index 00000000..1bd050f1 --- /dev/null +++ b/.changeset/fresh-error-hooks-sync.md @@ -0,0 +1,5 @@ +--- +'posthog-flutter': patch +--- + +Reapply Dart automatic error-capture hooks when setup is called again with changed flags. diff --git a/posthog_flutter/lib/src/posthog.dart b/posthog_flutter/lib/src/posthog.dart index 35e8a56b..129d4657 100644 --- a/posthog_flutter/lib/src/posthog.dart +++ b/posthog_flutter/lib/src/posthog.dart @@ -43,6 +43,9 @@ class Posthog { /// To listen for feature flag load events, provide an `onFeatureFlags` /// callback in the [PostHogConfig]. /// + /// Repeated setup reapplies Dart error-tracking hooks. Native settings still + /// require [close] before setup because the native SDKs ignore repeated setup. + /// /// Returns a [Future] that completes when platform setup has finished. /// /// **Example:** @@ -83,7 +86,8 @@ class Posthog { } void _installFlutterIntegrations(PostHogConfig config) { - // Install exception autocapture if enabled + _uninstallFlutterIntegrations(); + if (config.errorTrackingConfig.captureFlutterErrors || config.errorTrackingConfig.capturePlatformDispatcherErrors || config.errorTrackingConfig.captureIsolateErrors) { diff --git a/posthog_flutter/test/repeated_setup_hooks_test.dart b/posthog_flutter/test/repeated_setup_hooks_test.dart new file mode 100644 index 00000000..906e3036 --- /dev/null +++ b/posthog_flutter/test/repeated_setup_hooks_test.dart @@ -0,0 +1,209 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:posthog_flutter/posthog_flutter.dart'; +import 'package:posthog_flutter/src/posthog_flutter_platform_interface.dart'; + +import 'posthog_flutter_platform_interface_fake.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late PosthogFlutterPlatformFake platform; + FlutterExceptionHandler? originalFlutterHandler; + ErrorCallback? originalPlatformHandler; + + setUp(() async { + platform = PosthogFlutterPlatformFake(); + PosthogFlutterPlatformInterface.instance = platform; + await Posthog().close(); + originalFlutterHandler = FlutterError.onError; + FlutterError.onError = (_) {}; + if (!kIsWeb) { + originalPlatformHandler = PlatformDispatcher.instance.onError; + PlatformDispatcher.instance.onError = (_, __) => true; + } + }); + + tearDown(() async { + await Posthog().close(); + FlutterError.onError = originalFlutterHandler; + if (!kIsWeb) { + PlatformDispatcher.instance.onError = originalPlatformHandler; + } + }); + + void reportFlutterError({bool silent = false}) { + FlutterError.reportError(FlutterErrorDetails( + exception: StateError('repeated setup repro'), + stack: StackTrace.current, + silent: silent, + )); + } + + for (final reuseConfig in [false, true]) { + test('disables Flutter hook on repeated setup (reuse=$reuseConfig)', + () async { + final upstream = FlutterError.onError; + final first = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true; + await Posthog().setup(first); + reportFlutterError(); + expect(platform.capturedExceptions, hasLength(1)); + platform.capturedExceptions.clear(); + + final second = reuseConfig ? first : PostHogConfig('test-token'); + second.errorTrackingConfig.captureFlutterErrors = false; + await Posthog().setup(second); + expect(Posthog().config, same(second)); + + reportFlutterError(); + expect(platform.capturedExceptions, isEmpty); + expect(FlutterError.onError, same(upstream)); + }); + } + + test('disables PlatformDispatcher hook on repeated setup', () async { + final upstream = PlatformDispatcher.instance.onError; + final first = PostHogConfig('test-token') + ..errorTrackingConfig.capturePlatformDispatcherErrors = true; + await Posthog().setup(first); + PlatformDispatcher.instance.onError!( + StateError('before'), StackTrace.current); + expect(platform.capturedExceptions, hasLength(1)); + platform.capturedExceptions.clear(); + + await Posthog().setup(PostHogConfig('test-token')); + PlatformDispatcher.instance.onError!( + StateError('after'), StackTrace.current); + expect(platform.capturedExceptions, isEmpty); + expect(PlatformDispatcher.instance.onError, same(upstream)); + }, skip: kIsWeb); + + test('adds PlatformDispatcher hook while Flutter hook is installed', + () async { + final first = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true; + await Posthog().setup(first); + + final second = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true + ..errorTrackingConfig.capturePlatformDispatcherErrors = true; + await Posthog().setup(second); + expect(Posthog().config, same(second)); + + PlatformDispatcher.instance.onError!( + StateError('after'), StackTrace.current); + expect(platform.capturedExceptions, hasLength(1)); + }, skip: kIsWeb); + + test('replaces silent-error policy on repeated setup', () async { + final first = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true; + await Posthog().setup(first); + + final second = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true + ..errorTrackingConfig.captureSilentFlutterErrors = true; + await Posthog().setup(second); + + reportFlutterError(silent: true); + expect(platform.capturedExceptions, hasLength(1)); + }); + + test('mutating the original silent-error policy takes effect', () async { + final config = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true; + await Posthog().setup(config); + config.errorTrackingConfig.captureSilentFlutterErrors = true; + await Posthog().setup(config); + + reportFlutterError(silent: true); + expect(platform.capturedExceptions, hasLength(1)); + }); + + test('first installation can happen on second setup', () async { + await Posthog().setup(PostHogConfig('test-token')); + await Posthog().setup(PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true); + + reportFlutterError(); + expect(platform.capturedExceptions, hasLength(1)); + }); + + test('identical repeated setup does not duplicate capture', () async { + final config = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true; + await Posthog().setup(config); + await Posthog().setup(config); + + reportFlutterError(); + expect(platform.capturedExceptions, hasLength(1)); + }); + + for (final platformHandler in [false, true]) { + test( + 'repeated setup preserves wrapped handlers (platform=$platformHandler)', + () async { + var upstreamCalls = 0; + var wrapperCalls = 0; + final config = PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = !platformHandler + ..errorTrackingConfig.capturePlatformDispatcherErrors = platformHandler; + + if (platformHandler) { + PlatformDispatcher.instance.onError = (_, __) { + upstreamCalls++; + return true; + }; + } else { + FlutterError.onError = (_) => upstreamCalls++; + } + await Posthog().setup(config); + if (platformHandler) { + final delegate = PlatformDispatcher.instance.onError!; + PlatformDispatcher.instance.onError = (error, stack) { + wrapperCalls++; + return delegate(error, stack); + }; + } else { + final delegate = FlutterError.onError!; + FlutterError.onError = (details) { + wrapperCalls++; + delegate(details); + }; + } + + for (var i = 0; i < 3; i++) { + await Posthog().setup(config); + } + if (platformHandler) { + expect( + PlatformDispatcher.instance.onError!( + StateError('after'), StackTrace.current), + isTrue, + ); + } else { + reportFlutterError(); + } + expect(platform.capturedExceptions, hasLength(1)); + expect(upstreamCalls, 1); + expect(wrapperCalls, 1); + }, skip: kIsWeb && platformHandler); + } + + test('close then setup applies changed hook flags', () async { + await Posthog().setup(PostHogConfig('test-token') + ..errorTrackingConfig.captureFlutterErrors = true); + await Posthog().close(); + await Posthog().setup(PostHogConfig('test-token') + ..errorTrackingConfig.capturePlatformDispatcherErrors = true); + + reportFlutterError(); + expect(platform.capturedExceptions, isEmpty); + PlatformDispatcher.instance.onError!( + StateError('after'), StackTrace.current); + expect(platform.capturedExceptions, hasLength(1)); + }, skip: kIsWeb); +} From 14b25c700105838ab5a960e3347a494ee85d0df2 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Sep 2026 16:04:38 +0200 Subject: [PATCH 3/5] test: cover isolate hooks across repeated setup --- .../repeated_setup_isolate_hooks_test.dart | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 posthog_flutter/test/repeated_setup_isolate_hooks_test.dart diff --git a/posthog_flutter/test/repeated_setup_isolate_hooks_test.dart b/posthog_flutter/test/repeated_setup_isolate_hooks_test.dart new file mode 100644 index 00000000..f955eccd --- /dev/null +++ b/posthog_flutter/test/repeated_setup_isolate_hooks_test.dart @@ -0,0 +1,59 @@ +@TestOn('vm') +library; + +import 'dart:async'; +import 'dart:isolate'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:posthog_flutter/posthog_flutter.dart'; +import 'package:posthog_flutter/src/posthog_flutter_platform_interface.dart'; + +import 'posthog_flutter_platform_interface_fake.dart'; + +void main() { + test('repeated setup turns the isolate hook off and back on', () async { + final results = ReceivePort(); + final isolate = await Isolate.spawn( + _exerciseIsolateHook, + results.sendPort, + errorsAreFatal: false, + onExit: results.sendPort, + ); + addTearDown(() { + isolate.kill(priority: Isolate.immediate); + results.close(); + }); + + expect(await results.first, [1, 0, 1]); + }); +} + +Future _exerciseIsolateHook(SendPort results) async { + final observedErrors = ReceivePort(); + final errors = StreamIterator(observedErrors); + Isolate.current.addErrorListener(observedErrors.sendPort); + final platform = PosthogFlutterPlatformFake(); + PosthogFlutterPlatformInterface.instance = platform; + + try { + final counts = []; + for (final enabled in [true, false, true]) { + await Posthog().setup(PostHogConfig('test-token') + ..errorTrackingConfig.captureIsolateErrors = enabled); + platform.capturedExceptions.clear(); + Timer.run(() => throw StateError('isolate hook enabled=$enabled')); + await errors.moveNext(); + // Error listeners use separate ports; let the SDK's queued callback run. + await Future.delayed(Duration.zero); + counts.add(platform.capturedExceptions.length); + } + results.send(counts); + } catch (error, stack) { + results.send('$error\n$stack'); + } finally { + await Posthog().close(); + Isolate.current.removeErrorListener(observedErrors.sendPort); + await errors.cancel(); + observedErrors.close(); + } +} From 87ffa7c5338a4bb03aae515d5b4f0ab731e00529 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Sep 2026 17:28:37 +0200 Subject: [PATCH 4/5] fix: correct changeset package after merging main --- .changeset/fresh-error-hooks-sync.md | 2 +- .changeset/quiet-handlers-forward.md | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .changeset/quiet-handlers-forward.md diff --git a/.changeset/fresh-error-hooks-sync.md b/.changeset/fresh-error-hooks-sync.md index 1bd050f1..eb1147eb 100644 --- a/.changeset/fresh-error-hooks-sync.md +++ b/.changeset/fresh-error-hooks-sync.md @@ -1,5 +1,5 @@ --- -'posthog-flutter': patch +'posthog_flutter': patch --- Reapply Dart automatic error-capture hooks when setup is called again with changed flags. diff --git a/.changeset/quiet-handlers-forward.md b/.changeset/quiet-handlers-forward.md deleted file mode 100644 index af7d4cd5..00000000 --- a/.changeset/quiet-handlers-forward.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"posthog_flutter": patch ---- - -Preserve chained error handlers after teardown and prevent duplicate exception captures when error tracking restarts. From 426feeeede1bceabe2be9cf04e807528d724b636 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Sep 2026 17:35:12 +0200 Subject: [PATCH 5/5] docs: clarify settings reapplied by repeated setup --- posthog_flutter/lib/src/posthog.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/posthog_flutter/lib/src/posthog.dart b/posthog_flutter/lib/src/posthog.dart index 129d4657..8ae0234f 100644 --- a/posthog_flutter/lib/src/posthog.dart +++ b/posthog_flutter/lib/src/posthog.dart @@ -43,8 +43,9 @@ class Posthog { /// To listen for feature flag load events, provide an `onFeatureFlags` /// callback in the [PostHogConfig]. /// - /// Repeated setup reapplies Dart error-tracking hooks. Native settings still - /// require [close] before setup because the native SDKs ignore repeated setup. + /// Repeated setup reapplies Dart-side settings such as the error-tracking + /// hooks. Most native settings still require [close] before setup because + /// the native SDKs ignore repeated setup. /// /// Returns a [Future] that completes when platform setup has finished. ///