This text was written by an AI, but reviewed by a human. The problem is real, please read it.
Summary
We've been experiencing crashes affecting a significant portion of our Android users in production after integrating react-native-screenshot-prevent. The error is:
IllegalStateException: The specified child already has a parent.
You must call removeView() on the child's parent first.
I haven't opened a PR yet because I'm still validating the fix in production, but I wanted to report the issues as soon as possible given the user impact.
Root Causes Found
1. disableSecureView used hardcoded root view instead of actual parent
// ORIGINAL — crashes if overlayLayout was reparented (e.g. after onHostPause)
ViewGroup rootView = (ViewGroup) activity.getWindow().getDecorView().getRootView();
rootView.removeView(overlayLayout); // IllegalStateException if parent != rootView
onHostPause adds overlayLayout to rootView, but the overlay may have a different actual parent by the time disableSecureView runs. The fix is to always remove from overlayLayout.getParent().
2. enableSecureView decoded the bitmap on the UI thread
decodeImageUrl() (which opens a network connection) was called synchronously before dispatching to runOnUiThread. This blocks the UI thread and can also cause the overlay to be created in an inconsistent state.
3. onHostPause / onHostResume cycle causes double-attach crash
onHostPause adds the overlay to rootView. If enableSecureView is called again before onHostResume cleans up, createOverlay is skipped (overlayLayout != null), but the next onHostPause tries to rootView.addView(overlayLayout) when it already has a parent — crash.
4. onHostDestroy is wired up in ScreenshotPreventImpl but never called from the old arch module
// android/src/oldArch/java/.../RNScreenshotPreventModule.java
@Override
public void onHostDestroy() {
// Cleanup if needed ← ScreenshotPreventImpl.onHostDestroy() is never called!
}
The new onHostDestroy() method added in ScreenshotPreventImpl is dead code for old architecture users.
5. New arch module has no lifecycle listener at all
android/src/newArch/java/.../RNScreenshotPreventModule.java does not implement LifecycleEventListener, meaning onHostPause and onHostResume logic in ScreenshotPreventImpl is completely bypassed for New Architecture apps.
6. Timing race between background bitmap decode and disableSecureView
Even after moving bitmap decode to a background thread, there's a window:
enableSecureView called → background thread starts (network decode can take 200–500ms)
- Component unmounts →
disableSecureView called → overlayLayout == null, noop
- Background thread finishes → overlay is permanently created and never removed
Proposed Fixes (being validated in production)
- Always use
((ViewGroup) overlayLayout.getParent()).removeView(overlayLayout) instead of assuming rootView is the parent
- Guard every removal with
overlayLayout.getParent() != null before casting and removing
- Decode the bitmap in a background thread before dispatching to UI thread
- Wire
onHostDestroy in old arch module to ScreenshotPreventImpl.onHostDestroy()
- Add
LifecycleEventListener to the new arch module
- Add a cancellation flag to the background thread in
enableSecureView to prevent late overlay creation after disableSecureView was already called
Environment
react-native-screenshot-prevent: 1.2.2
- React Native: 0.77+
- Architecture: Old Arch (confirmed), New Arch (likely affected too)
- Platform: Android only
This text was written by an AI, but reviewed by a human. The problem is real, please read it.
Summary
We've been experiencing crashes affecting a significant portion of our Android users in production after integrating
react-native-screenshot-prevent. The error is:IllegalStateException: The specified child already has a parent.
You must call removeView() on the child's parent first.
I haven't opened a PR yet because I'm still validating the fix in production, but I wanted to report the issues as soon as possible given the user impact.
Root Causes Found
1.
disableSecureViewused hardcoded root view instead of actual parentonHostPauseaddsoverlayLayouttorootView, but the overlay may have a different actual parent by the timedisableSecureViewruns. The fix is to always remove fromoverlayLayout.getParent().2.
enableSecureViewdecoded the bitmap on the UI threaddecodeImageUrl()(which opens a network connection) was called synchronously before dispatching torunOnUiThread. This blocks the UI thread and can also cause the overlay to be created in an inconsistent state.3.
onHostPause/onHostResumecycle causes double-attach crashonHostPauseadds the overlay torootView. IfenableSecureViewis called again beforeonHostResumecleans up,createOverlayis skipped (overlayLayout != null), but the nextonHostPausetries torootView.addView(overlayLayout)when it already has a parent — crash.4.
onHostDestroyis wired up inScreenshotPreventImplbut never called from the old arch moduleThe new
onHostDestroy()method added inScreenshotPreventImplis dead code for old architecture users.5. New arch module has no lifecycle listener at all
android/src/newArch/java/.../RNScreenshotPreventModule.javadoes not implementLifecycleEventListener, meaningonHostPauseandonHostResumelogic inScreenshotPreventImplis completely bypassed for New Architecture apps.6. Timing race between background bitmap decode and
disableSecureViewEven after moving bitmap decode to a background thread, there's a window:
enableSecureViewcalled → background thread starts (network decode can take 200–500ms)disableSecureViewcalled →overlayLayout == null, noopProposed Fixes (being validated in production)
((ViewGroup) overlayLayout.getParent()).removeView(overlayLayout)instead of assumingrootViewis the parentoverlayLayout.getParent() != nullbefore casting and removingonHostDestroyin old arch module toScreenshotPreventImpl.onHostDestroy()LifecycleEventListenerto the new arch moduleenableSecureViewto prevent late overlay creation afterdisableSecureViewwas already calledEnvironment
react-native-screenshot-prevent: 1.2.2