Simplecursortreeadapter and Cursorloader for Expandablelistview

SimpleCursorTreeAdapter and CursorLoader for ExpandableListView

So i figured out that I needed to map loaderids to groupPositions and this solved my issue:

Here is my Fragment class which implements the CursorLoader

public class GroupsListFragment extends ExpandableListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {

private final String DEBUG_TAG = getClass().getSimpleName().toString();

private static final String[] CONTACTS_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME };

private static final String[] GROUPS_PROJECTION = new String[] {
ContactsContract.Groups.TITLE, ContactsContract.Groups._ID };

GroupsAdapter mAdapter;

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

populateContactList();

// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
Loader loader = getLoaderManager().getLoader(-1);
if (loader != null && !loader.isReset()) {
getLoaderManager().restartLoader(-1, null, this);
} else {
getLoaderManager().initLoader(-1, null, this);
}
}

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created.
Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id);
CursorLoader cl;
if (id != -1) {
// child cursor
Uri contactsUri = ContactsContract.Data.CONTENT_URI;
String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
+ " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '') AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
+ " = ? ))";
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
+ " COLLATE LOCALIZED ASC";
String[] selectionArgs = new String[] { String.valueOf(id) };

cl = new CursorLoader(getActivity(), contactsUri,
CONTACTS_PROJECTION, selection, selectionArgs, sortOrder);
} else {
// group cursor
Uri groupsUri = ContactsContract.Groups.CONTENT_URI;
String selection = "((" + ContactsContract.Groups.TITLE
+ " NOTNULL) AND (" + ContactsContract.Groups.TITLE
+ " != '' ))";
String sortOrder = ContactsContract.Groups.TITLE
+ " COLLATE LOCALIZED ASC";
cl = new CursorLoader(getActivity(), groupsUri,
GROUPS_PROJECTION, selection, null, sortOrder);
}

return cl;
}

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in.
int id = loader.getId();
Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id);
if (id != -1) {
// child cursor
if (!data.isClosed()) {
Log.d(DEBUG_TAG, "data.getCount() " + data.getCount());

HashMap<Integer,Integer> groupMap = mAdapter.getGroupMap();
try {
int groupPos = groupMap.get(id);
Log.d(DEBUG_TAG, "onLoadFinished() for groupPos " + groupPos);
mAdapter.setChildrenCursor(groupPos, data);
} catch (NullPointerException e) {
Log.w("DEBUG","Adapter expired, try again on the next query: "
+ e.getMessage());
}
}
} else {
mAdapter.setGroupCursor(data);
}

}

public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// is about to be closed.
int id = loader.getId();
Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id);
if (id != -1) {
// child cursor
try {
mAdapter.setChildrenCursor(id, null);
} catch (NullPointerException e) {
Log.w("TAG", "Adapter expired, try again on the next query: "
+ e.getMessage());
}
} else {
mAdapter.setGroupCursor(null);
}
}

/**
* Populate the contact list
*/
private void populateContactList() {
// Set up our adapter
mAdapter = new GroupsAdapter(getActivity(),this,
android.R.layout.simple_expandable_list_item_1,
android.R.layout.simple_expandable_list_item_1,
new String[] { ContactsContract.Groups.TITLE }, // Name for group layouts
new int[] { android.R.id.text1 },
new String[] { ContactsContract.Contacts.DISPLAY_NAME }, // Name for child layouts
new int[] { android.R.id.text1 });

setListAdapter(mAdapter);
}
}

And here is my adapter which subclasses SimpleCursorTreeAdapter

public class GroupsAdapter extends SimpleCursorTreeAdapter {

private final String DEBUG_TAG = getClass().getSimpleName().toString();

private ContactManager mActivity;
private GroupsListFragment mFragment;

protected final HashMap<Integer, Integer> mGroupMap;

// Note that the constructor does not take a Cursor. This is done to avoid
// querying the database on the main thread.
public GroupsAdapter(Context context, GroupsListFragment glf,
int groupLayout, int childLayout, String[] groupFrom,
int[] groupTo, String[] childrenFrom, int[] childrenTo) {

super(context, null, groupLayout, groupFrom, groupTo, childLayout,
childrenFrom, childrenTo);
mActivity = (ContactManager) context;
mFragment = glf;
mGroupMap = new HashMap<Integer, Integer>();
}

@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
// Given the group, we return a cursor for all the children within that group
int groupPos = groupCursor.getPosition();
int groupId = groupCursor.getInt(groupCursor
.getColumnIndex(ContactsContract.Groups._ID));
Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos);
Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId);

mGroupMap.put(groupId, groupPos);

Loader loader = mActivity.getLoaderManager().getLoader(groupId);
if ( loader != null && !loader.isReset() ) {
mActivity.getLoaderManager().restartLoader(groupId, null, mFragment);
} else {
mActivity.getLoaderManager().initLoader(groupId, null, mFragment);
}

return null;
}

//Accessor method
public HashMap<Integer, Integer> getGroupMap() {
return mGroupMap;
}

}

SimpleCursorTreeAdapter for ExpandableListView

The initial Cursor just needs the parent items. Here I usually use a Cursor that gives me distinct values.

You need to extend SimpleCursorTreeAdapter and overwrite getChildrenCursor(). Here I use the selected distinct value to return a Cursor that contains all rows of that type.

Simply said, you need a cursor for the parent value and one for every child. Most of the time the child cursor is the same with a where clause for the selected parent.

ExpandableListView using SimpleCursorTreeAdapter

I found this example on the android docs for SimpleCursorTreeAdapter here check it out.

CursorLoader and CursorTreeAdapter

call destroyLoader() on onGroupCollapsed(). But it doesn't solve problem completely. Looking on CursorTreeAdapter realization make me sure to not use it with CursorLoader.

It's really better to extend BaseExpandableAdapter. This way allow to avoid unnecessary (in case of using loaders) usage of content observers. In my custom adapter i'm keep created loaders in sparse array and destroy them on group collapse. Works well :)

CursorTreeAdapter with search implementation

I've looked into your issue, and unfortunately I don't have time to replicate your setup. In generic terms, however, You should be able to save your constraint, and then in 'getChildrenCursor', run a query against that constraint:

Cursor getChildrenCursor(Cursor groupCursor) {
if (mConstraint == null || mConstraint.isEmpty()) {
// Normal query
} else {
// Constrained query
}

}

I'm not certain, but I'm pretty sure that getChildrenCursor() will get called in response to a change of the parent cursor when you return the cursor in the filterQueryProvider(). You then just manage the null/filled state of the constraint.

Details:

In your filterList function, instead of doing a complicated procedure, just call runQueryOnBackgroundThread(constraint);. This will automatically offload database work to the background. Save your constraint in your filterQueryProvider:

String s = '%' + constraint.toString() + '%';
mConstraint = s;

For the query, it just depends on what you're trying to get out of the database - a quick adjustment to the code you posted runs the query like so:

String selection = ContactsContract.CommonDataKinds.Email.DATA
+ " NOT LIKE ''";
if (constraint != null) {
selection += " AND " + ContactsContract.CommonDataKinds.Email.DATA + " LIKE ?";
}
cl = new CursorLoader(getApplicationContext(),
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
PROJECTION, selection, constraint, sortOrder);

The one thing I'm not too sure about is the auto expand thing you have going, My filter works but you need to collapse and open the list again to see the change.

Android ExpandableListView with SimpleCursorTreeAdapter not refreshing

I'm sorry. Strangely I've found an answer now after I've already asked for it here. Not my intention. But mybe someone will use it.

I've reinitialized my Cursor and changed it for the Adapter

getAllCat = db.getAllCategories(1);
cursorTreeAdapter.changeCursor(getAllCat);

This is what onClick block looks like now:

public void onClick(View v) {

db.open();
db.addMyCategory("TestCat7", "");
getAllCat = db.getAllCategories(1);
cursorTreeAdapter.changeCursor(getAllCat);
cursorTreeAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "KLIK", Toast.LENGTH_SHORT).show();
}

Any better suggestions are still welcome.
Thanks



Related Topics



Leave a reply



Submit