jul
jul

Reputation: 37474

Issue with Fragment lifecycle: getView() is null

I have a MainActivity, with a layout including two FrameLayout to load Fragments, one for a top bar with a vertical menu, another one for the content.

Everything works properly, excepted when I click the device's Home button and start the app again.

For instance when I start the app, click the menu option 2, which replaces the content fragment by an instance of the SomeFragment shown below, then click the device's Home button, then start the app again, it restarts the SomeFragment Fragment as expected, but my app crashes in the TopBarFragment, returning an error because the getView() is null.

I guess I'm messing with the Fragment lifecycle, but I can't find the solution.

Note: in order to test my app I checked the option Don't keep activities in my device's developer options.

Anybody can help?

EDIT

When restarting the app, onResume is not called in the TopBarFragment instance. Why? The instance called by

((TopBarFragment)getActivity().getSupportFragmentManager()
    .findFragmentByTag("top_bar_fragment")).setSelectedButton(1);

seems to be 'empty'... Why?

EDIT 2: more experiments

When clicking on the device's Home button, onDetach and onDestroyView are called in TopBarFragment, so why is onCreateView not called again when restarting the app?

EDIT 3: more experiments

When restarting the app, the MainActivity onCreate is called, so

fragmentTransaction.replace(R.id.content_container, homeFragment, "");

is executed. However, it loads SomeFragment, which is nice because it was the last Fragment loaded in the content_container, but unexpected because MainActivity onCreate is called and it should load HomeFragment...

stacktrace

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.my.package/com.my.package.MainActivity}: java.lang.NullPointerException
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1967)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992)
    at android.app.ActivityThread.access$600(ActivityThread.java:127)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1158)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4511)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
    at com.my.package.TopBarFragment.setSelectedButton(TopBarFragment.java:345)
    at com.my.package.SomeFragment.onCreateView(SomeFragment.java:50)
    at android.support.v4.app.Fragment.performCreateView(Fragment.java:1460)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:911)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1088)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1070)
    at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1861)
    at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:547)
    at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1136)
    at android.app.Activity.performStart(Activity.java:4480)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1940)
    ... 11 more

MainActivity.java

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);     

        setContentView(R.layout.main);      

        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();

        Fragment homeFragment = new HomeFragment();
        fragmentTransaction.replace(R.id.content_container, homeFragment, "");

        Fragment topBarFragment = new TopBarFragment();
        fragmentTransaction.replace(R.id.top_bar_container, topBarFragment, "top_bar_fragment");

        fragmentTransaction.commit();
    }
}

main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <FrameLayout
        android:id="@+id/content_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="44dp" />

    <FrameLayout
        android:id="@+id/top_bar_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false" />

</RelativeLayout>

TopBarFragment.java

public class TopBarFragment extends Fragment{   

    private int mSelectedMenuOption = 0;

    private LinearLayout mVerticalMenu;

    private Boolean mMenuIsOpen = true;

    private ImageButton btn_01, btn_02; // there are more


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        return inflater.inflate(R.layout.top_bar, container, false);
    }

    @Override
    public void onActivityCreated (Bundle savedInstanceState){

        super.onActivityCreated(savedInstanceState);

        btn_01 = (ImageButton) getView().findViewById(R.id.btn_01);
        btn_02 = (ImageButton) getView().findViewById(R.id.btn_02);

        btn_01.setOnClickListener(mButtonClickListener);
        btn_02.setOnClickListener(mButtonClickListener);

        mVerticalMenu = (LinearLayout) getView().findViewById(R.id.vertical_menu);

        toggleMenu(0);

        Button btn_menu = (Button) getView().findViewById(R.id.btn_menu);
        btn_menu.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // toggle vertical menu             

            }
        });
    }


    private OnClickListener mButtonClickListener = new OnClickListener()
    {

        @Override
        public void onClick(View v) {

            /* ... */

            if(!v.isSelected()){

                FragmentTransaction fragmentTransaction = getActivity().getSupportFragmentManager().beginTransaction();

                switch(v.getId()){

                case R.id.btn_01:

                    Fragment homeFragment = new HomeFragment();     
                    fragmentTransaction.replace(R.id.content_container,homeFragment, "");
                    fragmentTransaction.commit();

                    break;

                case R.id.btn_02:

                    Fragment someFragment = new SomeFragment();     
                    fragmentTransaction.replace(R.id.content_container, someFragment, "");
                    fragmentTransaction.commit();

                    break;      
                }
            }
        }
    };

    public void setSelectedButton(int i){

        // Crashes when starting the app, clicking on btn_02 to load a SomeFragment instance, clicking on the 
        // device's Home button, and starting the app again: getView() is null
        // why?

        /* ... */            

        mSelectedMenuOption = i;
    }
}

SomeFragment

public class SomeFragment extends Fragment{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        ((TopBarFragment)getActivity().getSupportFragmentManager().findFragmentByTag("top_bar_fragment")).setSelectedButton(1);

        return inflater.inflate(R.layout.some_fragment, container, false);
    }
}

Upvotes: 3

Views: 7677

Answers (2)

user
user

Reputation: 87064

From the stacktrace posted it seems that calling the setSelectedButton(1) method on the TopBarFragment(where you most likely update/work with its views) is done before its content view is built. Move the TopBarFragment replace transaction before the transaction to replace the content view so its view its built before the content fragment try to access it.

Upvotes: 1

TronicZomB
TronicZomB

Reputation: 8747

You are doing this in the correct order according to the docs, onActivityCreated is after onCreateView. I know I have seen lots of working examples on SO where users use getView(), but I have always used getActivity() instead and put my declarations in my onStart and it always works fine for me. You could try the following code:

@Override
public void onActivityCreated (Bundle savedInstanceState){

    super.onActivityCreated(savedInstanceState);

    btn_01 = (ImageButton) getActivity().findViewById(R.id.btn_01);
    btn_02 = (ImageButton) getActivity().findViewById(R.id.btn_02);

    btn_01.setOnClickListener(mButtonClickListener);
    btn_02.setOnClickListener(mButtonClickListener);

    mVerticalMenu = (LinearLayout) getActivity().findViewById(R.id.vertical_menu);

    toggleMenu(0);

    Button btn_menu = (Button) getActivity().findViewById(R.id.btn_menu);
    btn_menu.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            // toggle vertical menu             

        }
    });
}

Upvotes: 0

Related Questions