PostHog / PostHog/posthog-flutter
iOS session replay records a Mapbox `UiKitView` as a black box, even with `maskAllPlatformViews = false`
Nobody has claimed this yet.
- Dominant language
- Dart
- Stars
- 97
- Forks
- 85
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 31
Description
Version
5.44.2
Steps to Reproduce
Environment: posthog_flutter 5.44.2, mapbox_maps_flutter 2.24.3, Flutter stable, iOS 27.0 simulator (iPhone 18 Pro).
- Add a
mapbox_maps_flutterMapWidgetto a screen. On iOS that plugin renders a plainUiKitView— the only iOS branch, with no texture-mode option (lib/src/mapbox_maps_platform.dart:80):
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/mapbox_maps',
...
- Enable session replay with every mask off:
await Posthog().setup(
PostHogConfig('<token>')
..sessionReplay = true
..sessionReplayConfig = (PostHogSessionReplayConfig()
..maskAllTexts = false
..maskAllImages = false
..maskAllPlatformViews = false
..captureNativeScreens = true),
);
- Wrap the app in
PostHogWidget, run on an iOS simulator, and open the screen with the map. - Watch the resulting replay.
Wrapping the map in PostHogPlatformView(privacy: PostHogPlatformViewPrivacy.capture) makes no difference.
Related
Related to #151, which tracked platform view recording and was closed in July 2026 after PR #453 and #473. On iOS the reveal path only covers WKWebView, so the Mapbox case from that thread still reproduces — a maintainer had linked the "Flutter iOS Google Maps does not visible in session replay" question there as the same problem.
Questions
- Is non-WebView platform view capture planned for iOS, or is WebView-only the intended scope? If the latter,
maskAllPlatformViewsshould document it. - Could
drawHierarchy(in:afterScreenUpdates:)be gated on no masked platform view intersecting the crop rect, keeping the leak protection for mixed screens while letting the fully-unmasked case work?
Expected Result
The map appears in the replay, since platform view masking is disabled globally and maskAllPlatformViews is documented as a global opt-out with no iOS caveat.
Actual Result
The map is a solid black rectangle exactly its own size, while the debug log claims the opposite:
flutter: Found 0 masked and 1 captured platform view rect(s)
That mismatch is what makes this hard to diagnose from the app side: the SDK reports the view as captured, and the replay shows it masked.
Cause. Dart classifies the UiKitView as captured and asks native to screenshot the rect (screenshot_capturer.dart:610). captureOneNative searches that rect for a WKWebView, finds none, and returns nil (darwin/.../PosthogFlutterPlugin.swift:744):
// No WKWebView found for the captured rect. Returning nil here
// keeps this safe: drawHierarchy over the full window would
// include any masked CALayer-backed platform view overlapping
// the crop region and leak it into replay.
onResult(nil)
Dart reads nil as "capture failed" and paints the mask anyway (screenshot_capturer.dart:419):
if (bytes == null) {
_imageMaskPainter.drawMaskedImage(canvas, [fallbackMask], pixelRatio);
return;
}
So the view round-trips captured → declined → re-masked, with the log line emitted before the decline.
I have only tested Mapbox, so I can't speak to other plugins, but the WebView-only check is not Mapbox-specific. I have not tested Android either, though its captureOneNative composites platform-view SurfaceViews rather than restricting to web views (android/.../PosthogFlutterPlugin.kt:1216-1221), so the platforms appear to differ.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with screenshot_capturer.dart at lines 419 and 610, then inspect darwin/.../PosthogFlutterPlugin.swift around captureOneNative at line 744. Reproduce the Mapbox UiKitView case and trace the captured-to-nil-to-mask path. Done means an unmasked iOS platform view appears in replay while masked platform views remain protected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart, flutter, ios, swift
- Domain
- mobile, observability
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100