Search code examples
androidandroid-fragmentsonbackpressed

onBackPressed to fragment(A, B, C)


I have main activity and three fragment (one, two, three). And all this fragments extands BaseFragment:

public abstract class BaseFragment extends Fragment {
private Toolbar mToolbar;
private ActionBar mActionBar;

@Override
@CallSuper
public void onAttach(Activity context) {
    super.onAttach(context);

    mToolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
    mActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
    //mActionBar.setTitle(setMyTitle());
    //Log.i("BaseFragment", "onAttach = "+getBackStackCount());
    resetToolbarNavigation(getBackStackCount()!=0);
}

//protected abstract String setMyTitle();

@Override
@CallSuper
public void onDetach() {
    super.onDetach();
    Log.i("BaseFragment", "onDetach = " + (getBackStackCount() - 1));
    resetToolbarNavigation((getBackStackCount() - 1 )!= 0);
}

private int getBackStackCount() {
    int b = getActivity().getSupportFragmentManager().getBackStackEntryCount();
    Log.i("BaseFragment", "getBackStackCount = "+b);

    return b;
}

private void resetToolbarNavigation(boolean backNavigationEnabled) {
    mActionBar.setDisplayHomeAsUpEnabled(backNavigationEnabled);
    Log.i("BaseFragment", "resetToolbarNavigation");
    if (backNavigationEnabled) {
        mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i("resetToolbarNavigation", "setNavigationOnClickListener");
                getActivity().onBackPressed();
            }
        });
    }
    else {
        ((StartPageActivity) getActivity()).initToolbar();
        ((StartPageActivity) getActivity()).syncState();
    }
}

}

But when i click back arrow i got Exeption

09-22 19:28:13.233    5643-5643/com.test.mylist E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NullPointerException
        at com.test.exemple.BaseFragment$1.onClick(BaseFragment.java:56)
        at android.view.View.performClick(View.java:2485)
        at android.view.View$PerformClick.run(View.java:9080)
        at android.os.Handler.handleCallback(Handler.java:587)
        at android.os.Handler.dispatchMessage(Handler.java:92)
        at android.os.Looper.loop(Looper.java:130)
        at android.app.ActivityThread.main(ActivityThread.java:3683)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:507)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
        at dalvik.system.NativeStart.main(Native Method)

onBackPressed inside main activity

    @Override
public void onBackPressed() {
        getSupportFragmentManager().popBackStack();
        Log.d("StartPageActivity", "onBackPressed " + getSupportFragmentManager().getBackStackEntryCount());

}

Can you help me? Where is the error? What am I doing wrong?


Solution

  • Not sure why you're getting that exact exception, but your approach to use the fragment is not so good in the way it's designed and that could bring this type of problems. So here I leave you some advices that has been useful to me when implementing apps with fragments:

    • Separate responsibilities: Activity should take care of it's stuff and Fragment should only be responsible for Fragment's functionalities. To achieve this you could make your Fragment publish interfaces with the methods you need the Activity implements and then just use it in the Fragment. For example in your code you have strong relations in your fragment form your Activity. Method resetToolbarNavigation is interacting with Views that belong to the Activity, notice that method is not doing nothing that is Fragment responsibility, so that entire method should be implemented in the activity, then you can publish a interface in your BaseFragment for example:

      //Get the instance of the activity implementation 
      private BaseFragmentActions baseFragmentActions;
      //Publish your interface with the methods your Activity will implement 
      public interface BaseFragmentActions {
          public void resetToolbarNavigation(boolean backNavigationEnabled);
      }
      
      //In your onAttach get the implementation from your activity
      @Override
      public void onAttach(Activity activity) {
          super.onAttach(activity);
      
          // This makes sure that the container activity has implemented
          // the callback interface. If not, it throws an exception
          try {
              baseFragmentActions = (BaseFragmentActions)activity;
          } catch (ClassCastException e) {
              throw new ClassCastException(activity.toString() + " must implement interface BaseFragmentActions");
          }
      }
      

    Then in your Activity just implement the method and use it in your fragment like baseFragmentActions.resetToolbarNavigation(true); Notice that this way BaseFragment doesn't care about the activity, just ask that the activity that contain it have to implement the methods it needs. To achieve this you can use EventBus too.

    • The transactions between fragments should be done always in the Activity that contain it. A Fragment should not know/cares about which Fragment should be loaded after.

    • Use the concept "Activity knows it's Fragments but Fragment don't know it's Activity"

    You can do it this way and you can have information of your Activity in your Fragment but that could bring bigs headaches like this one.

    Hope it helps you as concept for future design and sorry for my English