Summary
On React Native 0.86, makeImageFromView / view snapshots log a NoSuchMethodException for every ReactViewGroup drawn and silently skip overflow (rounded-corner / overflow: hidden) clipping in the captured image.
ViewScreenshotService.drawChildren() invokes ReactViewGroup.dispatchOverflowDraw(Canvas) by reflection:
// packages/skia/android/src/main/java/com/shopify/reactnative/skia/ViewScreenshotService.java
Method method = ReactViewGroup.class.getDeclaredMethod("dispatchOverflowDraw", cArg);
method.setAccessible(true);
method.invoke(group, canvas);
RN 0.86 rewrote ReactViewGroup in Kotlin and removed dispatchOverflowDraw. Overflow clipping moved into the dispatchDraw override:
// react-native 0.86: ReactViewGroup.kt
override fun dispatchDraw(canvas: Canvas) {
if (_overflow != Overflow.VISIBLE || getTag(R.id.filter) != null) {
clipToPaddingBox(this, canvas) // BackgroundStyleApplicator.clipToPaddingBox(view, canvas)
}
super.dispatchDraw(canvas)
}
So on RN 0.86 the reflected method no longer exists, every call throws NoSuchMethodException, is caught, and logs:
E SkiaScreenshot: couldn't invoke dispatchOverflowDraw() on ReactViewGroup
java.lang.NoSuchMethodException: com.facebook.react.views.view.<ReactViewGroup>.dispatchOverflowDraw [class android.graphics.Canvas]
It is caught (no crash), but (1) snapshots of views with overflow: hidden / border-radius no longer get the overflow clip applied, and (2) it fires once per ReactViewGroup per capture (observed ~25+ times on a single screen), throwing an exception each time on the draw path.
This reproduces on both minified (R8) and non-minified builds: it is a straight API removal, not obfuscation. (dispatchOverflowDraw is absent from RN 0.86's ReactViewGroup.kt and from a non-R8 dex, confirmed with dexdump.)
Environment
@shopify/react-native-skia: 2.9.1-next.1 (also present in 2.10.0, identical reflection)
- react-native: 0.86.0
- Expo SDK 57, New Architecture + Hermes, Android
Repro
Snapshot (makeImageFromView) any <View style={{ overflow: 'hidden', borderRadius: N }}> subtree on RN 0.86 and watch logcat (adb logcat | grep SkiaScreenshot).
Suggested fix
Keep the existing reflection for RN < 0.86, and fall back to RN 0.86+'s BackgroundStyleApplicator.clipToPaddingBox(view, canvas) (also reflected, to avoid a hard version dependency) gated on the public getOverflow() state. clipToPaddingBox is @JvmStatic public fun clipToPaddingBox(view: View, canvas: Canvas) in RN 0.86.
if (group instanceof ReactViewGroup) {
try {
// RN < 0.86: private ReactViewGroup.dispatchOverflowDraw(Canvas)
Method method = ReactViewGroup.class.getDeclaredMethod("dispatchOverflowDraw", Canvas.class);
method.setAccessible(true);
method.invoke(group, canvas);
} catch (NoSuchMethodException removedInRn086) {
// RN >= 0.86: overflow clipping moved to dispatchDraw() ->
// BackgroundStyleApplicator.clipToPaddingBox(view, canvas), gated on overflow != visible.
try {
String overflow = ((ReactViewGroup) group).getOverflow();
if (overflow != null && !overflow.equals("visible")) {
Class<?> bsa = Class.forName("com.facebook.react.uimanager.BackgroundStyleApplicator");
Method clip = bsa.getMethod("clipToPaddingBox", View.class, Canvas.class);
clip.invoke(null, group, canvas); // @JvmStatic -> null receiver
}
} catch (Exception e) {
Log.e(TAG, "couldn't apply RN>=0.86 overflow clip (clipToPaddingBox)", e);
}
} catch (Exception e) {
Log.e(TAG, "couldn't invoke dispatchOverflowDraw() on ReactViewGroup", e);
}
}
Caching the resolved Method/Class in static fields (instead of resolving per child, per frame) would also remove the current per-draw reflection + exception cost.
Note: this is an untested suggestion pointing at the RN 0.86 replacement API, the exact binding should be validated against the maintainers' supported RN version matrix, since BackgroundStyleApplicator is an internal RN API.
Summary
On React Native 0.86,
makeImageFromView/ view snapshots log aNoSuchMethodExceptionfor everyReactViewGroupdrawn and silently skip overflow (rounded-corner /overflow: hidden) clipping in the captured image.ViewScreenshotService.drawChildren()invokesReactViewGroup.dispatchOverflowDraw(Canvas)by reflection:RN 0.86 rewrote
ReactViewGroupin Kotlin and removeddispatchOverflowDraw. Overflow clipping moved into thedispatchDrawoverride:So on RN 0.86 the reflected method no longer exists, every call throws
NoSuchMethodException, is caught, and logs:It is caught (no crash), but (1) snapshots of views with
overflow: hidden/ border-radius no longer get the overflow clip applied, and (2) it fires once perReactViewGroupper capture (observed ~25+ times on a single screen), throwing an exception each time on the draw path.This reproduces on both minified (R8) and non-minified builds: it is a straight API removal, not obfuscation. (
dispatchOverflowDrawis absent from RN 0.86'sReactViewGroup.ktand from a non-R8 dex, confirmed withdexdump.)Environment
@shopify/react-native-skia: 2.9.1-next.1 (also present in 2.10.0, identical reflection)Repro
Snapshot (
makeImageFromView) any<View style={{ overflow: 'hidden', borderRadius: N }}>subtree on RN 0.86 and watch logcat (adb logcat | grep SkiaScreenshot).Suggested fix
Keep the existing reflection for RN < 0.86, and fall back to RN 0.86+'s
BackgroundStyleApplicator.clipToPaddingBox(view, canvas)(also reflected, to avoid a hard version dependency) gated on the publicgetOverflow()state.clipToPaddingBoxis@JvmStatic public fun clipToPaddingBox(view: View, canvas: Canvas)in RN 0.86.Caching the resolved
Method/Classin static fields (instead of resolving per child, per frame) would also remove the current per-draw reflection + exception cost.Note: this is an untested suggestion pointing at the RN 0.86 replacement API, the exact binding should be validated against the maintainers' supported RN version matrix, since
BackgroundStyleApplicatoris an internal RN API.