Search code examples
androidandroid-fragmentsnavigation-drawernavigationview

IllegalStateException: Fragment has not been attached yet when using BottomNavigation


I am using a BottomNavigationDrawer/BottomSheetDialog from the following example, where the BottomNavigationDrawer switches between multiple fragments inside a single activity.

While implementing the BottomNavigationDrawer, I realized that when the fragments were swapped without using the menu, i.e. by clicking a button in a fragment, this left the menu selection in an inconsistent state. Also, the solution of using a function in the main activity to trigger the FragmentTransaction caused crashes.

public void resetNavDefault(){
        if(fragment == null) {
            fragment = new BottomNavigationDrawerFragment();
            navigationView = fragment.getNavigationView();
        } else navigationView = fragment.getNavigationView();
        //navigationView.getMenu().getItem(0).setChecked(true);
        Log.d(TAG, "resetNavDefault: setting check");
        MenuItem item = navigationView.getMenu().getItem(0);
        navigationView.getMenu().performIdentifierAction(item.getItemId(), 0);
    }

I realised that this was caused by getSupportFragmentManager being null and/or the fragment not being attached, and using getChildFragmentManager was not the right solution, as I do not use nested fragments, only multiple fragments in a single activity.

I also tried implementing this workaround where the onDetach is overridden to make the childFragmentManager accessible.

This fix prevents the app from crashing with an NPE (due to the if (!isAdded()) return; condition) but leaves the button that launches the new fragment unresponsive (the button does not work) .

How do I navigate between two fragments using FragmentTransaction without causing an NPE, and without leaving the menu selection in an inconsistent state?

My BottomNavigationDrawer implementation is as follows:

BottomNavigationDrawer

public class BottomNavigationDrawerFragment extends BottomSheetDialogFragment {

    BottomNavigationDrawerFragment fragment;
    public NavigationView navigationView;
    ImageView close, menu;
    Statuser statuser;
    RevivDatabase database;
    String email, fname, lname;
    TextView txtUsername, txtEmail;

    private static String TAG = "BottomNavDrawerFragment";

    public BottomNavigationDrawerFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view =  inflater.inflate(R.layout.fragment_bottomsheet, container, false);
        txtUsername = view.findViewById(R.id.txtUsername);
        txtEmail = view.findViewById(R.id.txtEmail);
        navigationView = view.findViewById(R.id.navigation_view);
        database = RevivDatabase.getDatabase(getActivity());
        statuser = database.revivDao().getUserDetails();
        fname = statuser.getFname();
        lname = statuser.getLname();
        email = statuser.getEmail();
        txtEmail.setText(email);
        txtUsername.setText(fname+" "+lname);
        setRetainInstance(true);
        close = view.findViewById(R.id.imgClose);
        fragment = this;
        close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                fragment.dismiss();
            }
        });
        navigationView.setItemIconTintList(null);
        navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
                menuItem.setChecked(true);
                int id = menuItem.getItemId();
                FragmentManager manager;
                try {
                    manager = getActivity().getSupportFragmentManager();
                } catch (NullPointerException e) {
                    if (!isAdded()) return false;
                    manager = getChildFragmentManager();
                    Log.e(TAG, "onNavigationItemSelected: ", e);
                }
                switch (id){
                    case R.id.app_bar_incident:
                        navigationView.getMenu().findItem(id).setChecked(true);
                        manager.beginTransaction().replace(R.id.containerFrameLayout, new FragmentRevivIncidentDashboard()).commit();
                        Toast.makeText(getContext(), "Request Incident", Toast.LENGTH_SHORT).show();
                        fragment.dismiss();
                        break;
                    case R.id.app_bar_housecall:
                        navigationView.getMenu().findItem(id).setChecked(true);
                        manager.beginTransaction().replace(R.id.containerFrameLayout, new FragmentRevivHousecallDashboard()).commit();
                        fragment.dismiss();
                        Toast.makeText(getContext(), "Request Housecall", Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.settings:
                        navigationView.getMenu().findItem(id).setChecked(true);
                        manager.beginTransaction().replace(R.id.containerFrameLayout, new FragmentRevivSettingsMain()).commit();
                        Toast.makeText(getActivity().getApplicationContext(), "Settings", Toast.LENGTH_SHORT).show();
                        fragment.dismiss();
                        break;

                }
                return true;
            }
        });

        return view;
    }

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

        try {
            Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
            childFragmentManager.setAccessible(true);
            childFragmentManager.set(this, null);

        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public NavigationView getNavigationView() {
        return navigationView;
    }
}

My Error Message

09-13 00:26:32.996 28025-28025/com.package.name E/AndroidRuntime: FATAL EXCEPTION: main Process: com.package.name, PID: 28025 java.lang.IllegalStateException: Fragment has not been attached yet. at android.support.v4.app.Fragment.instantiateChildFragmentManager(Fragment.java:2386) at android.support.v4.app.Fragment.getChildFragmentManager(Fragment.java:842) at com.package.name.Fragments.BottomNavigationDrawerFragment$2.onNavigationItemSelected(BottomNavigationDrawerFragment.java:108) at android.support.design.widget.NavigationView$1.onMenuItemSelected(NavigationView.java:170) at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:840) at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:158) at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:991) at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:981) at android.support.v7.view.menu.MenuBuilder.performIdentifierAction(MenuBuilder.java:977) at com.package.name.Reviv.resetNavDefault(Reviv.java:644) at com.package.name.Fragments.FragmentRevivSettingsMain$1.onClick(FragmentRevivSettingsMain.java:77) at android.view.View.performClick(View.java:6303) at android.view.View$PerformClick.run(View.java:24828) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6798) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)


Solution

  • First implement the button click event in your MainActivity as in this page. Then assuming you have your BottomNavigationDrawerFragment instance in MainActivity, just get the view through this instance and make the setSelection call for navigation menu through this view as the button is clicked. Make sure that you are getting the view through instance after bottomNavDrawerFragment.show(..) call is made. Otherwise you will get a NPE error.