1

I have a section of my UX flow where I want to display a message for the user in a textview for 2 seconds in fragment A, and after that replace fragment A with fragment B. I accomplish this by doing as follows from within fragment A.

someTextView.setText("some message");

Handler mainLooperHandler = new Handler(Looper.getMainLooper());

mainLooperHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    ((MyActivity) getActivity()).gotoTheNextFragment();
                }
            }, 2000);

And in my activity the relevant method:

public void gotoTheNextFragment() {
    Fragment mFragmentB = new FragmentB();

    getSupportFragmentManager().beginTransaction()
            .replace(R.id.fragmentContainer, mFragmentB, "FRAGMENTB")
            .commit();
}

However this causes a few issues. First of all, if the user presses 'back' while the postdelayed is executing then the user gets a null pointer exception on getActivity. Second, the user sometimes gets the error "Can not perform this action after onSaveInstanceState" while trying to perform the transfer.

I know that if I change the 'commit' to 'commitAllowingStateLoss' then I'll avoid the second error, but this probably isn't best practice and I know that moving between fragments at all inside an asynctask isn't recommended. What is a better way of accomplishing this process (showing the message, waiting two seconds, and then replacing the fragment in a safe way that won't lead to crashes)?

Jon
  • 7,941
  • 9
  • 53
  • 105
  • Have you tried, Context.sendBroadcast (or some other way of notifications) to inform activity that you want to change Fragments. This way you can have variables in your activity that will limit what can be done during transition? – zveljkovic Apr 18 '16 at 19:01
  • Why don't you cancel the Handler task on onDestroyView() of your fragment ? – Shadab Ansari Apr 18 '16 at 19:02
  • Use snack bar or toast with msg like wait we are preparing or something so user won't press back or feel annoying. – Wasim K. Memon Apr 18 '16 at 19:04
  • @zveljkovic do you mean something like defining an interface inside fragment A and then subscribing to the interface in the activity? In that case, what checks would the activity make before calling the change fragment method to ensure that it was a legal transaction? Is it enough to simply check if (!isFinishing)? – Jon Apr 18 '16 at 19:06
  • I'm sorry, I just noted that you can access your activity so there is no need to broadcast the event. I would definitely make boolean flags for enabling/disabling transitions. I.e. if you detect back press just set canChangeFragment = false; and check it before actual switch. Whenever you identify source of problem, make a flag and check for it. Don't forget to make the flags sync if using multiple threads (http://stackoverflow.com/questions/5861894/how-to-synchronize-or-lock-upon-variables-in-java) – zveljkovic Apr 18 '16 at 19:29

3 Answers3

2

No need to define method gotoTheNextFragment in Activity. You can define in the fragment it self.

someTextView.setText("some message");

Handler mainLooperHandler = new Handler(Looper.getMainLooper());

mainLooperHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
               if(getActivity!=null){

               Fragment mFragmentB = new FragmentB();

               getActivity().getSupportFragmentManager().beginTransaction()
              .replace(R.id.fragmentContainer, mFragmentB, "FRAGMENTB")
              .commit();
            }
          }
        }, 2000);
Dharmaraj
  • 1,256
  • 13
  • 12
0

Don't use getActivity() everytime because it returns Activity instance only first time fragment is created else it returns null. So store that instance in onAttach() method of fragment and then use it for any purpose.

Use it like this then it should resolve nullpointerexception while you transition backwards:

public class YourFragment extends Fragment {

private Activity _activity;

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    _activity = getActivity();
}

}

So now instead of using getActivity() use it like this and also use commitAllowingStateLoss for the "Can not perform this action after onSaveInstanceState" error.

Fragment mFragmentB = new FragmentB();

_activity.getSupportFragmentManager().beginTransaction()
         .replace(R.id.fragmentContainer, mFragmentB, "FRAGMENTB")
         .commitAllowingStateLoss();
0

I wanted to avoid using commitAllowingStateLoss because that's not considered good practice so in the end I did it this way:

Defined an interface between the fragment and the activity and implemented the interface in the activity. When the activity receives the message through the interface it begins the fragment transaction. This still causes issues because the answer to change fragments arrives even if the activity performed onPause and we get the same onSaveInstanceState error. I could have added a check if the activity was in onPause, but then the message to change fragments would have been lost, and the user would have resumed the app on the original fragment instead of the one he wanted to go to.

So, the final solution was to define two booleans. The first boolean is to track the activity state:

 private boolean activityIsPaused = false;

set to true in onPause, set to false in onResume. If the boolean is false and the answer returned through the interface then immediately perform the fragment transaction. However, if the boolean is set to true then set another boolean - call it, for example, wantsToPerformFragmentTransaction - to true. In onResumeFragments (possibly it would work just as well in onResume - I didn't check), if the boolean wantsToPerformFragmentTransaction is true then set it to false and perform the fragment transaction.

Result: no error, no use of commitAllowingStateLoss and a consistent result where the user sees the fragment they were supposed to see.

Jon
  • 7,941
  • 9
  • 53
  • 105