Search code examples
androidandroid-fragmentsandroid-listviewandroid-listfragment

How to maintain ListView fragment state after orientation/configuration change?


I have a ListView fragment in my application that is experiencing some issues.

  1. When I scroll to a specific element in the list view, then rotate the device, the list view resets to the top of the list.
  2. When I enter multi-select mode in the list view, then rotate the device, the selected list items reset.

Here is my activity:

public class TestActivity extends ActionBarActivity
{
    protected static final String   FRAGMENT_TAG    = "TEST";

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

        Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FragmentManager fm = getFragmentManager();
        TestFragment f = (TestFragment)fm.findFragmentByTag(FRAGMENT_TAG);

        // If no fragment exists, then create a new one and add it!
        if (f == null)
        {
            fm.beginTransaction().add(R.id.fragment_holder, new TestFragment(), FRAGMENT_TAG)
                    .commit();
        }
    }
}

here is main_layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize" >
    </android.support.v7.widget.Toolbar>

    <FrameLayout
        android:id="@+id/fragment_holder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Here is my TestFragment class, with the misc. content removed:

public class TestFragment extends ListFragment
{
    @Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);

        // Sets the list up for multiple choice selection.
        ListView listView = getListView();
        listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
        listView.setMultiChoiceModeListener(this);
    }
}

I have seen two discussions for this. One is stating that I should be using setRetainInstance(true) in the fragment's onCreate(Bundle savedInstanceState) method. The other says I should be using the onSaveInstanceState(Bundle bundle) and onRestoreInstanceState(Bundle bundle) methods to keep track of stuff somehow. I would like to use the setRetainInstanceState(true) approach, but adding that into the project like so:

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

Does not work for me. What am I doing wrong here?


Solution

  • I would suggest a slightly different approach to adding a Fragment to your Activity.

    In your TestActivity class, check if the Bundle is null in the onCreate() method. Only add the Fragment if savedInstanceState is null. This will prevent your Activity from adding another instance of the same Fragment when the device's orientation is changed.

    public class TestActivity extends ActionBarActivity {
    
        protected static final String FRAGMENT_TAG = "TEST";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_layout);
    
            Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            if (savedInstanceState == null) { 
    
                getFragmentManager().beginTransaction()
                        .replace(R.id.fragment_holder, new TestFragment(), FRAGMENT_TAG)
                        .commit();
            }
    
        }
    
    }
    

    I would suggest not using the setRetainInstance(true) method for a Fragment that has a UI. Check the StackOverflow discussion here for an explanation of when to use setRetainInstance(true).