Skip to content

[Android] "The specified child already has a parent" crash — multiple root causes in ScreenshotPreventImpl.java #72

@JVGS1111

Description

@JVGS1111

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:

  1. enableSecureView called → background thread starts (network decode can take 200–500ms)
  2. Component unmounts → disableSecureView called → overlayLayout == null, noop
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions