2

General context:
I have a MainActivity with a MainFragment. MainFragment has a ViewPager and I use a FragmentStatePagerAdapter to display DetailFragments based on the content of a SqliteDatabase. The content of this database is visible and managable in LocationActivity. When I click on an item in LocationActivity I use intent.putextra to attach the position of the item and then display the correct position in the ViewPager in MainFragment. When I add an item in LocationActivity it saves the position of the item in SharedPreferences and I get the correct position in MainFragment to display the fragment of the item I just added.

Problem:
It works but when a configuration change occurs I lose the actual page selected of the ViewPager. So I save the position with onSaveInstanceState(), if it's not null I retrieve it in onCreateView() and in onViewCreated() I go through the if statements to get the position required for ViewPager.setCurrentItem() depending on the condition. But then onCreate() is called a second time, onSaveInstanceState() is then null and I lose the page that was selected before the configuration change. Do you know why is it called twice? What can I do to prevent that? Thanks for your help

Log

06-24 09:15:05.974 activityMain.MainFragment: onLoadFinished() onPageSelected() 2
06-24 09:15:08.276 activityMain.MainFragment: onSaveInstanceState() 2
06-24 09:15:08.320 activityMain.MainFragment: onCreate()
06-24 09:15:08.342 activityMain.MainFragment: onCreateView()
06-24 09:15:08.342 activityMain.MainFragment: onCreateView() savedInstanceState Position: 2
06-24 09:15:08.349 activityMain.MainFragment: onViewCreated() get position from mCurrentPositionState 2
06-24 09:15:08.349 activityMain.MainFragment: onActivityCreated()
06-24 09:15:08.394 activityMain.MainFragment: onCreate()
06-24 09:15:08.396 activityMain.MainFragment: onCreateView()
06-24 09:15:08.401 activityMain.MainFragment: onViewCreated()) get position from SharedPreferences 4
06-24 09:15:08.401 activityMain.MainFragment: onActivityCreated()
06-24 09:15:08.407 activityMain.MainFragment: onResume()
06-24 09:15:08.458 activityMain.MainFragment: restartCursorLoader
06-24 09:15:08.508 activityMain.MainFragment: onLoadFinished()
06-24 09:15:08.537 activityMain.MainFragment: onLoadFinished() setCurrentItem: 4

A minimal, complete, and verifiable example

MainFragment.java

public class MainFragment extends Fragment {

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

    private static final String TAG = MainFragment.class.getName();
    private static final String PAGE_SELECTED = "state_instance";

    private MainPagerAdapter mAdapter;
    private ViewPager mViewPager;

    private int mLocationPosition;

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

        if (savedInstanceState != null) {
            mLocationPosition = savedInstanceState.getInt(PAGE_SELECTED);
            Log.i(TAG, "onCreateView() savedInstanceState not null, position: " + mLocationPosition);
        } else {
            mLocationPosition = 0;
            Log.i(TAG, "onCreateView() savedInstanceState null, position: " + mLocationPosition);
        }

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

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mViewPager = (ViewPager) view.findViewById(R.id.view_pager);
        mAdapter = new MainPagerAdapter(getActivity().getSupportFragmentManager());
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mViewPager.setAdapter(mAdapter);
        mViewPager.setCurrentItem(mLocationPosition, false);
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
                mLocationPosition = position;
                Log.i(TAG, "onActivityCreated() mLocationPosition value: " + mLocationPosition);
        }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putInt(PAGE_SELECTED, mLocationPosition);
        Log.i(TAG, "onSaveInstanceState() mLocationPosition value: " + mLocationPosition);
        super.onSaveInstanceState(outState);
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragment_container, new MainFragment());
        fragmentTransaction.commit();
    }
}

MainDetailFragment

public class MainDetailFragment extends Fragment {

    private static final String TAG = MainDetailFragment.class.getName();
    private TextView mTextViewLocation;

    protected static final String ARGUMENT_PAGE = "location_page";
    protected static final String ARGUMENT_NAME = "location_name";

    private String mLocation;

    public MainDetailFragment() {
    }

    protected static MainDetailFragment newInstance(int page, String locationName) {
        MainDetailFragment mainDetailFragment = new MainDetailFragment();
        Bundle arguments = new Bundle();
        arguments.putInt(MainDetailFragment.ARGUMENT_PAGE, page);
        arguments.putString(MainDetailFragment.ARGUMENT_NAME, locationName);
        mainDetailFragment.setArguments(arguments);
        return mainDetailFragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments().containsKey(ARGUMENT_NAME)) {
            mLocation = getArguments().getString(ARGUMENT_NAME);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_main_detail, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mTextViewLocation = (TextView) view.findViewById(R.id.tv_city_name);
        mTextViewLocation.setText(mLocation);
    }
}

MainPagerAdapter

public class MainPagerAdapter extends FragmentStatePagerAdapter {

    private static final String TAG = MainPagerAdapter.class.getName();

    private static int NUM_ITEMS = 3;

    public MainPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    // Returns total number of pages
    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    // Returns the fragment to display for that page
    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0: 
                return MainDetailFragment.newInstance(0, "Paris");
            case 1: 
                return MainDetailFragment.newInstance(1, "London");
            case 2: 
                return MainDetailFragment.newInstance(2, "New York");
            default:
                return null;
        }
    }
}
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
thegrxp
  • 103
  • 1
  • 9
  • Can you create a simpler example which illustrates your problem with less code? See [mcve] for tips on how to do this. – Code-Apprentice Jun 24 '17 at 15:05
  • Thanks for your reply, I just edited my question and tried to make it more readable with less code and more info. – thegrxp Jun 24 '17 at 16:10
  • Note that **every line of code** must start with 4 extra spaces in addition to the spaces for your actual indenting. I have fixed this as best as I can tell and also added some missing closing braces. Please double check to ensure that the code you posted here will actually compile if anyone copies and pastes it. – Code-Apprentice Jun 24 '17 at 19:06
  • To help create a truly minimal, but still complete, code example, I often find it helpful to start a new project from scratch and gradually add the pieces that are relevant to my question. If you do this, I think you can still reduce the code you have given here. For example, the few lines for the Toolbar are irrelevant. – Code-Apprentice Jun 24 '17 at 19:08
  • @Code-Apprentice Oh I see, I'll create a new project as I don't think this code will compile. Thanks for your time and advice – thegrxp Jun 24 '17 at 19:15
  • It looks like you have an answer already. Looking at your code, I wonder what is the difference between `mLocationPosition` and `mPageSelected`? Do you really need both variables? – Code-Apprentice Jun 25 '17 at 07:03
  • Indeed I don't need both variables at all... I already got an answer but I wanted to improve the question in case someone needs it. – thegrxp Jun 25 '17 at 07:32
  • I appreciate the extra effort to improve your question. I am returning the favor by helping you improve your code. Not only do you not need both of these variables, but they are a potential source of a bug. You should remove one of them to prevent this headache. – Code-Apprentice Jun 25 '17 at 07:35
  • @Code-Apprentice, thank you! – thegrxp Jun 25 '17 at 07:45

2 Answers2

3

You need to make sure you're not adding your MainFragment a second time after the configuration change. You should update your Activity to look like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    if (savedInstanceState != null) {
        return;
    }

    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
    fragmentTransaction.replace(R.id.fragment_container, new MainFragment());
    fragmentTransaction.commit();
}

The reason for this is that the Activity itself already has a record of the MainFragment being added and will automatically restore it after a configuration change. If you perform the same transaction again, first you'll see the restored MainFragment starting up, then its going to get replaced by the new one from the new Fragment transaction and a new, different MainFragment is going to go through its own initialization process. That results in what appears to be multiple calls to onCreate for MainFragment.

Brian Yencho
  • 2,748
  • 1
  • 18
  • 19
  • Alternatively, you can reverse the condition of the `if` statement: `if (savedInstanceState == null) { /* Perform fragment transaction */ }`. – Code-Apprentice Jun 24 '17 at 19:55
  • 1
    @Brian Yencho, thanks a lot for this solution and clear explanation it solves my problem. – thegrxp Jun 24 '17 at 21:04
  • @Code-Apprentice, I might have a preference for this approach. I'll still edit my question soon in case someone needs it – thegrxp Jun 24 '17 at 21:11
-1

Call this in onCreate() of your MainFragment

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.i(TAG, "onCreate()");
    setRetainInstance(true);
}
Quick learner
  • 10,632
  • 4
  • 45
  • 55
  • you can also look this post https://stackoverflow.com/questions/7951730/viewpager-and-fragments-whats-the-right-way-to-store-fragments-state – Quick learner Jun 24 '17 at 14:59
  • `setRetainInstance(true)` does nothing in this case. I'll have a close look at your link thanks – thegrxp Jun 24 '17 at 16:12