androidandroid-fragmentsandroid-viewonresume

getParent() on Fragment View returns null in onResume() after rotation


I have the following code in onResume() in a custom fragment that extends ListFragment, which tries to find its parent:

@Override
public void onResume() {
    super.onResume();

    System.out.println("resume");
    View view = getView();
    System.out.println("view is " + (view != null ? "not " : "" ) + "null");
    ViewParent parent = view != null ? view.getParent() : null;
    System.out.println("parent is " + (parent != null ? "not " : "" ) + "null");
    if(view != null) {
        System.out.println(view.getRootView());
    }
}

When I run the app initially in the emulator it prints:

I/System.out: resume
I/System.out: view is not null
I/System.out: parent is not null
I/System.out: com.android.internal.policy.impl.PhoneWindow$DecorView{ ... }

However, after rotating the emulator, it prints:

I/System.out: resume
I/System.out: view is not null
I/System.out: parent is null
I/System.out: android.widget.FrameLayout{ ... }

Even though the fragment is visible just fine in the app, it appears as though it's view is not properly attached into the view tree yet in onResume() after rotating, since the root view is the FrameLayout which is the root view of the fragment and not the root view of the app.

What could be the cause of this and how can I properly access its ViewParent in/after onResume(), without resorting to something line getActivity().findViewById(someId)?


edit:

When I adjust the code in onResume() to print the root view in the next frame (using View.post()), it does find the proper root view (which means I can access the parent):

@Override
public void onResume() {
    super.onResume();

    System.out.println("resume");
    View view = getView();
    System.out.println("view is " + (view != null ? "not " : "" ) + "null");
    ViewParent parent = view != null ? view.getParent() : null;
    System.out.println("parent is " + (parent != null ? "not " : "" ) + "null");
    if(view != null) {
        System.out.println(view.getRootView());
        
        /**
         * added to see if it finds the proper root view in the next frame 
         */
        view.post(() -> {
            System.out.println("how about after post?");
            System.out.println(view.getRootView());
        });
    }
}

Now it prints:

I/System.out: view is not null
I/System.out: parent is null
I/System.out: android.widget.FrameLayout{ ... }
I/System.out: how about after post?
I/System.out: com.android.internal.policy.impl.PhoneWindow$DecorView{ ... }

But utilizing View.post() like that just feels like a nasty hack.


edit:

So, it appears the order of operations is not as I would have expected (might be a bug?).

But I may have found a solution, which I will post as an answer if it works as intended. The idea is to add a View.OnAttachStateChangeListener to the fragment view in onViewCreated() and thus find the parent in View.OnAttachStateChangeListener.onViewAttachedToWindow() instead of in Fragment.onResume().


Solution

  • I've managed to solve the issue by moving my logic from onResume() into an View.OnAttachStateChangeListener that I add in onViewCreated():

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    
        view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View view) {
                System.out.println(view.getParent());
            }
    
            @Override
            public void onViewDetachedFromWindow(View view) {
                view.removeOnAttachStateChangeListener(this);
            }
        };
    }
    

    Now it behaves as I intended.

    The take-away is that onResume() is not a reliable method in which to assume the fragment view is attached to the view tree already.