alex
alex

Reputation: 5731

"Can not perform this action after onSaveInstanceState" when fragment is replaced

In my project context, I have a Button b in a Fragment f(1) in an Activity a.

Fragment f(x) is an instance of F where content depends of argument x

I need to replace the current instance f(1) by an instance f(2) on b click event:

My problem is:

An Exception is throw on b click event only if a configuration state change occurs:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

Please, what is my error? I read many posts on this Exception but I don't found any solution


Edit: I just make a test without AsyncTask, see the code:

Try to rotate the screen and push the button

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.activity_main);
        if (state == null) {
            setFragment(1);
        }
    }

    private void setFragment(int id) {
        Bundle args = new Bundle();
        args.putInt("id", id);
        MyFragment myFragment = new MyFragment();
        myFragment.setArguments(args);
        myFragment.setListener(new MyFragment.Listener() {
            @Override
            public void onClick(int id) {
                setFragment(id);
            }
        });
        getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.fragment, myFragment)
            .commit();
    }

    public static class MyFragment extends Fragment{

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup view, Bundle state) {
            return new Button(getActivity()) {
                {
                    setText(String.valueOf(getArguments().getInt("id")));
                    setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            listener.onClick(getArguments().getInt("id") + 1);
                        }
                    });
                }
            };
        }

        private static interface Listener {
            public void onClick(int id);
        }

        private Listener listener;

        public void setListener(Listener listener) {
            this.listener = listener;
        }

    }

}

Upvotes: 1

Views: 3498

Answers (1)

sockeqwe
sockeqwe

Reputation: 15929

The problem is the way you are setting the listener. You are setting the listener, then you rotate your device from landscape to portrait. What happens after the rotation is:

  1. Android create a brand new instance of MainActivity.
  2. FragmentManager create a new instance of MyFragment internally and re-adds it automatically to the activity as it was before orientation change.
  3. If you click on the button, the listener will be called. However, the listener is the listener of the previous activity (before rotation) which has been destroyed.

So not only you have a Memory Leak (the old activity can not be garbage collected, because it's referenced from here) but you also get this error.

How to do it correctly: Well, the problem is NOT only setRetainInstanceState() you have not understood the Android Fragments lifecycle correctly. As mentioned above, Fragments are controlled by the FragmentManager (FragmentTransaction). So, yes everytime you rotate your screen a new Fragment instance will be created, but FragmentManager will attach them automatically for you (it's a little bit strange, but thats how Fragment works)

I would recommend to use an EventBus. The fragment will fire an Event onClick() and the activity will receive this event since it's subscribed. I recomment GreenDao EventBus. Otherwise you can have a look at the official docs, but from my point of view they are teaching not a good solution, because your fragment and activity are hardly connected (not modular). They say you should use onAttach() like you can see in the sample from the documentation: http://developer.android.com/guide/components/fragments.html#EventCallbacks

Btw. a similar problem can occur if you are not using Fragment arguments for "passing" data. For more details read this blog: http://hannesdorfmann.com/android/fragmentargs/

Upvotes: 2

Related Questions