Skip to content

feat: capture platform views in session replay screenshots#393

Open
marcorizza wants to merge 2 commits into
PostHog:mainfrom
marcorizza:issue/151
Open

feat: capture platform views in session replay screenshots#393
marcorizza wants to merge 2 commits into
PostHog:mainfrom
marcorizza:issue/151

Conversation

@marcorizza
Copy link
Copy Markdown

💡 Motivation and Context

Fixes #151.

Session Replay in Flutter was only capturing the Flutter render tree, so native platform views rendered on top of Flutter content were missing from recordings. In practice, this meant native overlays such as paywalls, maps, or web views could appear blank or invisible in session replay snapshots.

This PR adds an opt-in sessionReplayConfig.capturePlatformViews flag for session replay. When enabled, Flutter requests a screenshot from the native view hierarchy on iOS and Android and uses that image for replay snapshots, allowing native platform views to be included in recordings.

The option defaults to false because native platform view content cannot be automatically masked using Flutter widget masking rules.

💚 How did you test it?

  • Added a unit test to verify that sessionReplayConfig.capturePlatformViews is disabled by default and correctly serialized in the replay config map.
  • Manually verified on iOS and Android that native platform views are captured in session replay when sessionReplayConfig.capturePlatformViews is enabled.
  • Confirmed the existing Flutter-only behavior is preserved when the option is left disabled.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

@marcorizza marcorizza requested a review from a team as a code owner May 23, 2026 10:41
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 23, 2026

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt:572-606
**Soft-canvas fallback silently misses GPU-composited platform views**

`View.draw()` renders using a software `Canvas`, which cannot read content from hardware-accelerated surfaces like `SurfaceView`, `TextureView`, or Metal/Vulkan-backed views. This is the exact content type `capturePlatformViews` is meant to capture. On pre-API 26 devices, or whenever `PixelCopy` fails, this fallback will produce blank areas for the native platform views the user opted into capturing. A brief inline comment explaining this limitation would prevent confusion when developers debug unexpected blank regions on older API levels.

### Issue 2 of 2
posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt:608-613
**PNG compression runs on the main UI thread**

`bitmapToPng` is called directly inside the `PixelCopy` completion callback, which is dispatched to `Handler(Looper.getMainLooper())`. PNG encoding of a full-screen `ARGB_8888` bitmap (`Bitmap.CompressFormat.PNG, 100`) can take 100–300 ms on a typical device, blocking the main thread for the duration of every session-replay snapshot when `capturePlatformViews` is enabled. The same applies to the `captureNativeScreenshotFallback` path. Consider offloading the compression to a background thread (e.g. `Executors.newSingleThreadExecutor()`) and marshalling the byte-array result back to the main thread before calling `result.success()`.

Reviews (1): Last reviewed commit: "feat: capture platform views in session ..." | Re-trigger Greptile

Comment on lines +572 to +606
private fun captureNativeScreenshotFallback(
contentView: View,
cropLeft: Int,
cropTop: Int,
cropRight: Int,
cropBottom: Int,
logicalWidth: Int,
logicalHeight: Int,
result: Result,
) {
val contentBitmap =
Bitmap.createBitmap(contentView.width, contentView.height, Bitmap.Config.ARGB_8888)
contentView.draw(android.graphics.Canvas(contentBitmap))

val croppedBitmap =
Bitmap.createBitmap(
contentBitmap,
cropLeft,
cropTop,
cropRight - cropLeft,
cropBottom - cropTop,
)
contentBitmap.recycle()

val outputBitmap =
if (croppedBitmap.width == logicalWidth && croppedBitmap.height == logicalHeight) {
croppedBitmap
} else {
Bitmap.createScaledBitmap(croppedBitmap, logicalWidth, logicalHeight, true).also {
croppedBitmap.recycle()
}
}

result.success(bitmapToPng(outputBitmap))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Soft-canvas fallback silently misses GPU-composited platform views

View.draw() renders using a software Canvas, which cannot read content from hardware-accelerated surfaces like SurfaceView, TextureView, or Metal/Vulkan-backed views. This is the exact content type capturePlatformViews is meant to capture. On pre-API 26 devices, or whenever PixelCopy fails, this fallback will produce blank areas for the native platform views the user opted into capturing. A brief inline comment explaining this limitation would prevent confusion when developers debug unexpected blank regions on older API levels.

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt
Line: 572-606

Comment:
**Soft-canvas fallback silently misses GPU-composited platform views**

`View.draw()` renders using a software `Canvas`, which cannot read content from hardware-accelerated surfaces like `SurfaceView`, `TextureView`, or Metal/Vulkan-backed views. This is the exact content type `capturePlatformViews` is meant to capture. On pre-API 26 devices, or whenever `PixelCopy` fails, this fallback will produce blank areas for the native platform views the user opted into capturing. A brief inline comment explaining this limitation would prevent confusion when developers debug unexpected blank regions on older API levels.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +608 to +613
private fun bitmapToPng(bitmap: Bitmap): ByteArray {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
bitmap.recycle()
return outputStream.toByteArray()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 PNG compression runs on the main UI thread

bitmapToPng is called directly inside the PixelCopy completion callback, which is dispatched to Handler(Looper.getMainLooper()). PNG encoding of a full-screen ARGB_8888 bitmap (Bitmap.CompressFormat.PNG, 100) can take 100–300 ms on a typical device, blocking the main thread for the duration of every session-replay snapshot when capturePlatformViews is enabled. The same applies to the captureNativeScreenshotFallback path. Consider offloading the compression to a background thread (e.g. Executors.newSingleThreadExecutor()) and marshalling the byte-array result back to the main thread before calling result.success().

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt
Line: 608-613

Comment:
**PNG compression runs on the main UI thread**

`bitmapToPng` is called directly inside the `PixelCopy` completion callback, which is dispatched to `Handler(Looper.getMainLooper())`. PNG encoding of a full-screen `ARGB_8888` bitmap (`Bitmap.CompressFormat.PNG, 100`) can take 100–300 ms on a typical device, blocking the main thread for the duration of every session-replay snapshot when `capturePlatformViews` is enabled. The same applies to the `captureNativeScreenshotFallback` path. Consider offloading the compression to a background thread (e.g. `Executors.newSingleThreadExecutor()`) and marshalling the byte-array result back to the main thread before calling `result.success()`.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session Recordings: Platform Views (Native Views)

1 participant