Note that there are some explanatory texts on larger screens.

plurals
  1. POSave and restore collapsed state of ExpandableListActivity with SimpleCursorTreeAdapter
    text
    copied!<p>After spending some days now on research for this problem, I am finally giving up and post this question. Similar questions have been answered here (partly) and solutions were proposed, but non of them finally helped me. The difference to the discussed topics and my problem seems to be: Non of the others seems to use a <strong>SimpleCursorTreeAdapter</strong> to feed the list...</p> <p>Problem: When changing the orientation of my device (Samsung Galaxy S), the expand state of the groups is not preserved and restored. All groups appear closed and the list is scrolled to the top.</p> <p>The problem already is visible in the Android <strong>API demos</strong>. In case you have them installed, navigate to:</p> <p>Views --> Expandable Lists --> 2. Cursor (People): start the sample, expand any group, turn your device and the result shows the group in collapsed state.</p> <p>In the code below, I did the following: Took the code from the the API demo (ExpandableList2.java) and extended it with implementations for onSaveInstantState(), onRestoreInstanceState(), and onResume(). Implementations for these methods was taken from another discussion thread here (<a href="https://stackoverflow.com/questions/5713585/how-to-preserve-scroll-position-in-an-expandablelistview">How to preserve scroll position in an ExpandableListView</a>).</p> <pre><code>import android.app.ExpandableListActivity; import android.content.AsyncQueryHandler; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; import android.view.View; import android.widget.CursorTreeAdapter; import android.widget.ExpandableListView; import android.widget.SimpleCursorTreeAdapter; public class MainActivity extends ExpandableListActivity { private static final String LIST_STATE_KEY = "levelSelectListState"; private static final String LIST_POSITION_KEY = "levelSelectListPosition"; private static final String ITEM_POSITION_KEY = "levelSelectItemPosition"; private static final String[] CONTACTS_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME }; private static final int GROUP_ID_COLUMN_INDEX = 0; private static final String[] PHONE_NUMBER_PROJECTION = new String[] { Phone._ID, Phone.NUMBER }; private static final int TOKEN_GROUP = 0; private static final int TOKEN_CHILD = 1; private static final class QueryHandler extends AsyncQueryHandler { private CursorTreeAdapter mAdapter; public QueryHandler(Context context, CursorTreeAdapter adapter) { super(context.getContentResolver()); this.mAdapter = adapter; } @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { switch (token) { case TOKEN_GROUP: mAdapter.setGroupCursor(cursor); break; case TOKEN_CHILD: int groupPosition = (Integer) cookie; mAdapter.setChildrenCursor(groupPosition, cursor); break; } } } public class MyExpandableListAdapter extends SimpleCursorTreeAdapter { // Note that the constructor does not take a Cursor. This is done to // avoid querying the // database on the main thread. public MyExpandableListAdapter(Context context, int groupLayout, int childLayout, String[] groupFrom, int[] groupTo, String[] childrenFrom, int[] childrenTo) { super(context, null, groupLayout, groupFrom, groupTo, childLayout, childrenFrom, childrenTo); } @Override protected Cursor getChildrenCursor(Cursor groupCursor) { // Given the group, we return a cursor for all the children within // that group // Return a cursor that points to this contact's phone numbers Uri.Builder builder = Contacts.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, groupCursor.getLong(GROUP_ID_COLUMN_INDEX)); builder.appendEncodedPath(Contacts.Data.CONTENT_DIRECTORY); Uri phoneNumbersUri = builder.build(); mQueryHandler.startQuery(TOKEN_CHILD, groupCursor.getPosition(), phoneNumbersUri, PHONE_NUMBER_PROJECTION, Phone.MIMETYPE + "=?", new String[] { Phone.CONTENT_ITEM_TYPE }, null); return null; } } private QueryHandler mQueryHandler; private CursorTreeAdapter mAdapter; private Parcelable listState; private int listPosition; private int itemPosition; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set up our adapter mAdapter = new MyExpandableListAdapter( this, android.R.layout.simple_expandable_list_item_1, android.R.layout.simple_expandable_list_item_1, new String[] { Contacts.DISPLAY_NAME }, // Name for group // layouts new int[] { android.R.id.text1 }, new String[] { Phone.NUMBER }, // Number for child layouts new int[] { android.R.id.text1 }); setListAdapter(mAdapter); mQueryHandler = new QueryHandler(this, mAdapter); // Query for people mQueryHandler.startQuery(TOKEN_GROUP, null, Contacts.CONTENT_URI, CONTACTS_PROJECTION, Contacts.HAS_PHONE_NUMBER + "=1", null, null); } @Override protected void onDestroy() { super.onDestroy(); // Null out the group cursor. This will cause the group cursor and all // of the child cursors // to be closed. mAdapter.changeCursor(null); mAdapter = null; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); ExpandableListView listView = this.getExpandableListView(); listState = listView.onSaveInstanceState(); outState.putParcelable(LIST_STATE_KEY, listState); listPosition = listView.getFirstVisiblePosition(); outState.putInt(LIST_POSITION_KEY, listPosition); View itemView = listView.getChildAt(0); itemPosition = itemView == null ? 0 : itemView.getTop(); outState.putInt(ITEM_POSITION_KEY, itemPosition); } @Override protected void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); listState = state.getParcelable(LIST_STATE_KEY); listPosition = state.getInt(LIST_POSITION_KEY); itemPosition = state.getInt(ITEM_POSITION_KEY); } @Override protected void onResume() { super.onResume(); ExpandableListView listView = this.getExpandableListView(); if (listView != null) { if (listState != null) { // yes, this code is reached listView.onRestoreInstanceState(listState); listView.setSelectionFromTop(listPosition, itemPosition); } } } } </code></pre> <p>With the debugger I made sure, all these methods are actually called in the right order when the device orientation is changed. However: Restoring the state is without effect.</p> <p>Further debugging into it, I found that the content of the restored <strong>listState</strong> in onRestoreInstanceState() contains an empty array, while the <strong>listState</strong> in onSaveInstanceState() contains data in its array when put into the outState.</p> <p>So my assumption is, that the listState Parcelable is not persisted or retrieved correctly by the system...</p> <p>Can anybody point out what is going wrong, or provide a working example using the SimpleCursorTreeAdapter with "working" restore of expand/collapsed state?</p> <p>(Note: To reproduce this problem with the activity code above, you need to provide android.permission.READ_CONTACTS permission)</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload