Adamski
Adamski

Reputation: 3665

React native exception: The specified child already has a parent.

I'm getting the following error triggered by React Native. I'm pretty sure its linked to a custom view I am using.

The view is generated by some C++ code (via OpenGL) which is added to a ViewGroup and then incorporated into a React Native view. The first time the app loads, everything is fine. However, when some data changes, it seems that React wants to reload the view, and create a new instance of the view. I am checking if the view has a parent and if so, delete it, before returning the view. However, after the user selects new data, just as the views are about to update, the crash occurs.

The ViewManager code is as follows:

public class ReactJuceViewManager extends SimpleViewManager<JuceViewHolder> {

    public static final String REACT_CLASS = "ReactJuceView";
    private ReactApplicationContext reactContext;

    public ReactJuceViewManager(ReactApplicationContext reactContext)
    {
        super();
        this.reactContext = reactContext;
    }

    @Override
    public String getName()
    {
        return REACT_CLASS;
    }

    @Override
    protected JuceViewHolder createViewInstance(ThemedReactContext themedReactContext) {

        JuceBridge juceBridge = JuceBridge.getInstance(); 
        //JuceBridge manages the a HashMap of `JuceViewHolder`s, never deleting them once created

        Log.d("ReactJuceViewManager", "createViewInstance");
        JuceViewHolder v = juceBridge.getViewForComponent("Tuner", themedReactContext);

        Log.d("JuceViewHolder", v.getTag().toString());
        ViewGroup parent = (ViewGroup) v.getParent();
        if (parent != null)
        {
            Log.d("ReactJuceViewManager", "JuceViewHolder parent: " + v.getParent().toString());
            // Remove from parent
            Log.i("ReactJuceViewManager", "Removing JuceViewHolder instance from previous parent");
            parent.removeView(v);
        }
        else
        {
            Log.d("ReactJuceViewManager", "JuceViewHolder parent: null");
        }

        return v;
    }

}

.. and here is the stack trace:

E/unknown:React: Exception in native call from JS
                                                                       java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
                                                                           at android.view.ViewGroup.addViewInner(ViewGroup.java:4309)
                                                                           at android.view.ViewGroup.addView(ViewGroup.java:4145)
                                                                           at android.view.ViewGroup.addView(ViewGroup.java:4086)
                                                                           at com.facebook.react.views.view.ReactViewManager.addView(ReactViewManager.java:196)
                                                                           at com.facebook.react.views.view.ReactViewManager.addView(ReactViewManager.java:39)
                                                                           at com.facebook.react.uimanager.NativeViewHierarchyManager.manageChildren(NativeViewHierarchyManager.java:384)
                                                                           at com.facebook.react.uimanager.UIViewOperationQueue$ManageChildrenOperation.execute(UIViewOperationQueue.java:175)
                                                                           at com.facebook.react.uimanager.UIViewOperationQueue$2.run(UIViewOperationQueue.java:782)
                                                                           at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:829)
                                                                           at com.facebook.react.uimanager.UIViewOperationQueue.access$1500(UIViewOperationQueue.java:44)
                                                                           at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:868)
                                                                           at com.facebook.react.uimanager.GuardedChoreographerFrameCallback.doFrame(GuardedChoreographerFrameCallback.java:32)
                                                                           at com.facebook.react.uimanager.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:131)
                                                                           at android.view.Choreographer$CallbackRecord.run(Choreographer.java:856)
                                                                           at android.view.Choreographer.doCallbacks(Choreographer.java:670)
                                                                           at android.view.Choreographer.doFrame(Choreographer.java:603)
                                                                           at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
                                                                           at android.os.Handler.handleCallback(Handler.java:739)
                                                                           at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                           at android.os.Looper.loop(Looper.java:148)
                                                                           at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                                           at java.lang.reflect.Method.invoke(Native Method)
                                                                           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                                           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Upvotes: 0

Views: 3133

Answers (2)

Metu
Metu

Reputation: 1093

I encountered a similar problem and got it working by removing parent of the custom view if the view existed already.

You should probably return a new component on createViewInstance though.

static CustomView customView;

@Override
public CustomView createViewInstance(@NonNull ThemedReactContext reactContext) {
    if (customView == null) {
        customView = new CustomView(reactContext);
    } else {
        ViewGroup parent = (ViewGroup) customView.getParent();
        if (parent != null) {
            parent.removeView(customView);
        }
    }
    return customView;
}

Upvotes: 1

Adamski
Adamski

Reputation: 3665

I changed how the JuceBridge stores references to Components - storing ComponentPeerViews instead of the View that is the JuceViewHolder.

(See my fork of JUCE)

I then accessed the ComponentPeerView's parent which solved my issue:

protected ReactJuceView createViewInstance(ThemedReactContext themedReactContext) {
    JuceBridge juceBridge = JuceBridge.getInstance();
    juceBridge.setActivityContext(themedReactContext);

    ReactJuceView v = new ReactJuceView(themedReactContext);

    JuceBridge.ComponentPeerView componentPeerView = juceBridge.getPeerViewForComponent("MainComponent");
    if (componentPeerView != null) {

        Log.d ("ReactJuceViewManager", "Got componentPeerView");
        ViewGroup parent = (ViewGroup) componentPeerView.getParent();
        if (parent != null) {

            parent.removeView(componentPeerView);
        }
        v.addView(componentPeerView);
        Log.d( "ReactJuceViewManager", "About to attachOpenGlContext");
        MainActivity.attachOpenGLContext("MainComponent");
    }
    else
        Log.d("ReactJuceViewManager", "componentPeerView is null");

    return v;
}

Upvotes: 0

Related Questions