Migrating to CursorLoader & LoaderManager for AutoCompleteTextView with SQLiteDatabase - java

I have an AutoCompleteTextView, which shows dropdown list of suggestions taken from a SQLiteDatabase query. At the moment it uses SimpleCursorAdapter, however there are several problems with it (I have a separate question about the issue here: SimpleCursorAdapter issue - "java.lang.IllegalStateException: trying to requery an already closed cursor").
Nevertheless, I was advised to look in the direction of CursorLoader and LoaderManager, and I've tried it, yet couldn't make it work. I'd be grateful if someone would guide/recommend/show the right way of migrating my code below to CursorLoader/LoaderManager concept. Any kind of help very appreciated!
mAdapter = new SimpleCursorAdapter(this,
R.layout.dropdown_text,
null,
new String[]{CITY_COUNTRY_NAME},
new int[]{R.id.text}, 0);
mAutoCompleteTextView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> listView, View view, int position, long id) {
Cursor cursor = (Cursor) listView.getItemAtPosition(position);
cityCountryName = cursor.getString(cursor.getColumnIndexOrThrow(CITY_COUNTRY_NAME));
mAutoCompleteTextView.setText(cityCountryName);
JSONWeatherTask task = new JSONWeatherTask();
task.execute(new String[]{cityCountryName});
}
});
mAdapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence sequence) {
String constraint = sequence.toString();
String queryString = "SELECT " + ID + ", " + CITY_ID + ", " + CITY_COUNTRY_NAME + " FROM " + TABLE_1;
constraint = constraint.trim() + "%";
queryString += " WHERE " + CITY_COUNTRY_NAME + " LIKE ?";
String params[] = {constraint};
try {
Cursor cursor = database.rawQuery(queryString, params);
if (cursor != null) {
startManagingCursor(cursor);
cursor.moveToFirst();
return cursor;
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
});
mAutoCompleteTextView.setAdapter(mAdapter);
Here is the whole project for the reference (you can make a pull request there, if you wish): Open Weather App

Unfortunately, here on SO nobody came up with a solution, yet a colleague (he doesn't have an SO account) made a pull request with the migration fixes. It works perfectly, and I decided to post the correct answer here as well.
If you're interested in how these improvements look inside the actual code, you're welcome to visit the original open source project page on GitHub: Open Weather App
This is the new adapter:
mAdapter = new SimpleCursorAdapter(this,
R.layout.dropdown_text,
null,
new String[]{CITY_COUNTRY_NAME},
new int[]{R.id.text},0);
mAdapter.setFilterQueryProvider(new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence constraint) {
if (constraint != null) {
if (constraint.length() >= 3 && !TextUtils.isEmpty(constraint)) {
Bundle bundle = new Bundle();
String query = charArrayUpperCaser(constraint);
bundle.putString(CITY_ARGS, query);
getLoaderManager().restartLoader(0, bundle, MainActivity.this).forceLoad();
}
}
return null;
}
});
This is the onCreateLoader() callback:
#Override
public android.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
String s = args.getString(CITY_ARGS);
WeatherCursorLoader loader = null;
if (s != null && !TextUtils.isEmpty(s)) {
loader = new WeatherCursorLoader(this, database, s);
}
return loader;
}
Custom CursorLoader itself:
private static class WeatherCursorLoader extends CursorLoader {
private SQLiteDatabase mSQLiteDatabase;
private String mQuery;
WeatherCursorLoader(Context context, SQLiteDatabase cDatabase, String s) {
super(context);
mSQLiteDatabase = cDatabase;
mQuery = s + "%";
Log.d(TAG, "WeatherCursorLoader: " + mQuery);
}
#Override
public Cursor loadInBackground() {
return mSQLiteDatabase.query(TABLE_1, mProjection,
CITY_COUNTRY_NAME + " like ?", new String[] {mQuery},
null, null, null, "50");
}
}

Related

Querying a search through all contacts

I am trying to search up contacts with a search query, but something doesn't seem to be working:
public class Callbacks implements LoaderManager.LoaderCallbacks<Cursor> {
Context mContext;
String query;
public static final String QUERY_KEY = "query";
public static final String TAG = "Callbacks";
public Callbacks(Context context) { mContext = context; }
#Override
public Loader<Cursor> onCreateLoader(int loaderIndex, Bundle args) {
query = args.getString(QUERY_KEY);
ContentResolver cr = mContext.getContentResolver();
Uri contentUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, Uri.encode(query));
Uri fullContentUri = ContactsContract.Contacts.CONTENT_URI;
return new CursorLoader(
mContext, // Context
contentUri, // Search this
null, // Projection
null, // Selection
null, // Selection args
null // Sort Order
);
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
if (cursor.getCount() == 0) return;
cursor.moveToFirst();
Log.d(TAG, "Search Query: " + query);
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
Log.d(TAG, displayName);
}
cursor.close();
}
#Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
}
The full contact name list is Amy, Kevin, John, Joe, Oscar.
If I put "j" as the query, I only get John in the cursor (even though I'm supposed to get John and Joe):
D/Callbacks: Search Query: j
John
If I put "a" in the query, I get nothing in the cursor even though I should get Amy:
D/Callbacks: Search Query: a
If I replace the contentUri with fullContentUri (defined in the gist) so it's not a filtered uri, I get all the contacts as expected regardless of the query:
D/Callbacks: Search Query: j
Amy
Kevin
Joe
D/Callbacks: Oscar
John
so something is not right with my filtering...but according to this: https://developer.android.com/training/contacts-provider/retrieve-names it looks like I'm doing everything right.
First you are moving the cursor to the first item calling the method moveToFirst. Then you are moving directly to the second one calling the method moveToNext. In that case, you are missing the first element always. The solution should be to iterate directly from the first item:
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
if (cursor.getCount() == 0) return;
Log.d(TAG, "Search Query: " + query);
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
Log.d(TAG, displayName);
}
cursor.close();
}

why function are not getting executed in sequence?

Here is the problem...I have three different functions namely syncFirebaseContacts(); getAllContacts(); and compareContactUpdateList();. These function's execution order is very important because first function's output becomes input of another. As I have said I have placed these three function in my onCreate() method in sequence as described.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
datasource = new AdoreDataSource(this);
datasource.open();
syncFirebaseContacts();
getAllContacts();
compareContactUpdateList();
.......
But while debugging I found out that although pointer points syncFirebaseContacts() first but the code of getAllContact() is executed first and then compareContactUpdateList() is executed and after that syncFirebaseContacts() code is executed.
private void getAllContacts() {
String contactNumber;
ContentResolver cr = this.getContentResolver(); //Activity/Application android.content.Context
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if (cursor.moveToFirst()) {
do {
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
if (Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]{id}, null);
while (pCur.moveToNext()) {
contactNumber = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
//All Mapping Stuff to be done here..
contactNumber = contactNumber.replaceAll("\\s+","");
contactNumber = contactNumber.trim();
lst2.add(contactNumber);
break;
}
pCur.close();
}
} while (cursor.moveToNext());
}
}
private void syncFirebaseContacts() {
Firebase userLocationRef = new Firebase(Constants.FIREBASE_URL_USERS);
userLocationRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot dsp : dataSnapshot.getChildren()){
datasource.createContact(String.valueOf(dsp.getKey()));
lst1.add(String.valueOf(dsp.getKey()));
Log.e("Hello", String.valueOf(dsp.getKey()));
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
Log.e("Could not process", "Sorry");
}
});
}
private void compareContactsUpdateList() {
ArrayList<String> commonOfAll = new ArrayList<String>(lst1);
commonOfAll.retainAll(lst2);
ContentResolver cr = this.getContentResolver(); //Activity/Application android.content.Context
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
final ArrayList<ContactsList> contacts = new ArrayList<ContactsList>();
//List<SyncContacts> syncContacts = datasource.getAllContacts();
if (cursor.moveToFirst()) {
do {
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
if (Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]{id}, null);
while (pCur.moveToNext()) {
String nameOfContact = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String contactNumber = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactNumber = contactNumber.replaceAll("\\s+","");
contactNumber = contactNumber.trim();
Log.d("Name ", nameOfContact + contactNumber);
for(int j =0; j < commonOfAll.size(); j++){
if(contactNumber.equals(commonOfAll)){
Log.d("Name ", nameOfContact);
contacts.add(new ContactsList(contactNumber, nameOfContact, R.mipmap.ic_launcher));
}
}
break;
}
pCur.close();
}
} while (cursor.moveToNext());
}
final ContactsAdapterTwo contactsAdapterTwo = new ContactsAdapterTwo(this, contacts);
final GridView gridView = (GridView) findViewById(R.id.grid);
gridView.post(new Runnable() {
public void run() {
gridView.setAdapter(contactsAdapterTwo);
}
});
}
As my problem is that I want to compare two lists which is done in compareContactsUpdateList() by the line
ArrayList<String> commonOfAll = new ArrayList<String>(lst1);
commonOfAll.retainAll(lst2); as I found out that lst1 is always empty because syncFirebaseContacts() 's code is excuted at last
My applogies if my question length is too long or if my problem is trivial. But it is a big problem for me coz I am new to coding :-) please help me....Anyways Thanx in advance...

Saving elements in SQLite Android

I want to save DatabaseTableDay to my SQLite in anndroid applicatin but something gones wrong.
My DatabaseDAODay is:
public class DatabaseDAODay {
public static final String TAG = "DaysDAO";
// Database fields
private SQLiteDatabase mDatabase;
private DatabaseHelper mDbHelper;
private Context mContext;
private String[] mAllColumns = { DatabaseHelper.COLUMN_DAY_ID,
DatabaseHelper.COLUMN_DAY_NAME, DatabaseHelper.COLUMN_DAY_WEIGHT};
public DatabaseDAODay(Context context) {
this.mContext = context;
mDbHelper = new DatabaseHelper(context);
// open the database
try {
open();
} catch (SQLException e) {
Log.e(TAG, "SQLException on openning database " + e.getMessage());
e.printStackTrace();
}
}
public void open() throws SQLException {
mDatabase = mDbHelper.getWritableDatabase();
}
public void close() {
mDbHelper.close();
}
public DatabaseTableDay createDay(String name, float weight, Long id) {
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_DAY_NAME, name);
values.put(DatabaseHelper.COLUMN_DAY_WEIGHT, weight);
long insertId = id;
Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_DAYS, mAllColumns,
DatabaseHelper.COLUMN_DAY_ID + " = " + insertId, null, null,
null, null);
DatabaseTableDay newDay = new DatabaseTableDay();
if(cursor != null && cursor.moveToFirst()){
newDay = cursorToDay(cursor);
cursor.close();
Toast.makeText(mContext,"im here",Toast.LENGTH_LONG).show();
}
return newDay;
}
public void deleteDay(DatabaseTableDay databaseTableDay) {
long id = databaseTableDay.getId();
// delete all employees of this company
DatabaseDAOActivity databaseDAOActivity = new DatabaseDAOActivity(mContext);
List<DatabaseTableActivity> databaseTableActivities = databaseDAOActivity.getActivitiesOfDay(id);
if (databaseTableActivities != null && !databaseTableActivities.isEmpty()) {
for (DatabaseTableActivity e : databaseTableActivities) {
databaseDAOActivity.deleteActivity(e);
}
}
System.out.println("the deleted day has the id: " + id);
mDatabase.delete(DatabaseHelper.TABLE_DAYS, DatabaseHelper.COLUMN_DAY_ID
+ " = " + id, null);
}
public List<DatabaseTableDay> getAllDays() {
List<DatabaseTableDay> listDays = new ArrayList<DatabaseTableDay>();
Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_DAYS, mAllColumns,
null, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
DatabaseTableDay day = cursorToDay(cursor);
listDays.add(day);
cursor.moveToNext();
}
// make sure to close the cursor
cursor.close();
}
return listDays;
}
public DatabaseTableDay getDayById(long id) {
Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_DAYS, mAllColumns,
DatabaseHelper.COLUMN_DAY_ID + " = ?",
new String[] { String.valueOf(id) }, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
}
DatabaseTableDay databaseTableDay = cursorToDay(cursor);
return databaseTableDay;
}
protected DatabaseTableDay cursorToDay(Cursor cursor) {
DatabaseTableDay databaseTableDay = new DatabaseTableDay();
databaseTableDay.setId(cursor.getLong(0));
databaseTableDay.setName(cursor.getString(1));
databaseTableDay.setWeight(cursor.getLong(2));
return databaseTableDay;
}
}
and I try to save it by:
saveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DatabaseTableDay databaseTableDay = databaseDAODay.createDay(
editText.getText().toString(), 100f, new Long(myId));
List<DatabaseTableDay> list = databaseDAODay.getAllDays();
}
});
but list is empty anyway.
Probably the problem is createDay() method in DatabaseDAODay because if condition is always null and application doesn' run cursorToDay() method.
I had problem without condition and if there was only cursot.moveToFirst() and then cursorToDay() there was NullPoinerException - because coursor was null. I followed this and put condition !=null but actually nothing happens and List is always empty...
How should I solve my problem?
You are right about the problem being createDay(): instead of inserting a new day by using insert() you try to read from the database by using query()
Change your method like this:
public DatabaseTableDay createDay(String name, float weight, Long id) {
DatabaseTableDay dayToReturn;
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_DAY_NAME, name);
values.put(DatabaseHelper.COLUMN_DAY_WEIGHT, weight);
values.put(DatabaseHelper.COLUMN_DAY_ID, id);
long resID = mDatabase.insert(DatabaseHelper.TABLE_DAYS, null, values);
if (resID == -1)
{
// something went wrong, do error handling here
dayToReturn = null;
}
else
{
// no error: resID is "the row ID of the newly inserted row"
// you only need this info if you are using autoincrement
// not if you set the ID yourself
// all right, this will work -
// but somehow it hurts a little to retrieve an entry I just added.
// I'd like much more to simply use a constructor with all the values
// and create a new DatabaseTableDay instance
dayToReturn = getDayById(id);
}
return dayToReturn;
}
See also this link to documentation for SQLiteDatabase insert()

CursorTreeAdapter with search implementation

I'm making an application for android and I'm using CursorTreeAdapter as ExpandableListView. I want to use a search box for displaying the filtered ExpandableListView items. Like this:
Here's the code what I've written so far:
MainActivity.java:
package com.example.cursortreeadaptersearch;
import java.util.HashMap;
import android.app.SearchManager;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.widget.ExpandableListView;
import android.widget.SearchView;
import android.widget.SearchView.OnCloseListener;
import android.widget.SearchView.OnQueryTextListener;
import com.actionbarsherlock.app.SherlockFragmentActivity;
public class MainActivity extends SherlockFragmentActivity {
private SearchView search;
private MyListAdapter listAdapter;
private ExpandableListView myList;
private final String DEBUG_TAG = getClass().getSimpleName().toString();
/**
* The columns we are interested in from the database
*/
static final String[] CONTACTS_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_ID,
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Photo.CONTACT_ID };
static final String[] GROUPS_SUMMARY_PROJECTION = new String[] {
ContactsContract.Groups.TITLE, ContactsContract.Groups._ID,
ContactsContract.Groups.SUMMARY_COUNT,
ContactsContract.Groups.ACCOUNT_NAME,
ContactsContract.Groups.ACCOUNT_TYPE,
ContactsContract.Groups.DATA_SET };
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
search = (SearchView) findViewById(R.id.search);
search.setSearchableInfo(searchManager
.getSearchableInfo(getComponentName()));
search.setIconifiedByDefault(false);
search.setOnQueryTextListener(new OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
listAdapter.filterList(query);
expandAll();
return false;
}
#Override
public boolean onQueryTextChange(String query) {
listAdapter.filterList(query);
expandAll();
return false;
}
});
search.setOnCloseListener(new OnCloseListener() {
#Override
public boolean onClose() {
listAdapter.filterList("");
expandAll();
return false;
}
});
// get reference to the ExpandableListView
myList = (ExpandableListView) findViewById(R.id.expandableList);
// create the adapter
listAdapter = new MyListAdapter(null, MainActivity.this);
// attach the adapter to the list
myList.setAdapter(listAdapter);
Loader<Cursor> loader = getSupportLoaderManager().getLoader(-1);
if (loader != null && !loader.isReset()) {
runOnUiThread(new Runnable() {
public void run() {
getSupportLoaderManager().restartLoader(-1, null,
mSpeakersLoaderCallback);
}
});
} else {
runOnUiThread(new Runnable() {
public void run() {
getSupportLoaderManager().initLoader(-1, null,
mSpeakersLoaderCallback).forceLoad();
;
}
});
}
}
#Override
public void onResume() {
super.onResume();
getApplicationContext().getContentResolver().registerContentObserver(
ContactsContract.Data.CONTENT_URI, true,
mSpeakerChangesObserver);
}
#Override
public void onPause() {
super.onPause();
getApplicationContext().getContentResolver().unregisterContentObserver(
mSpeakerChangesObserver);
}
// method to expand all groups
private void expandAll() {
int count = listAdapter.getGroupCount();
for (int i = 0; i < count; i++) {
myList.expandGroup(i);
}
}
public LoaderManager.LoaderCallbacks<Cursor> mSpeakersLoaderCallback = new LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id);
CursorLoader cl = null;
HashMap<Integer, Integer> groupMap = listAdapter.getGroupMap();
if (id != -1) {
int groupPos = groupMap.get(id);
if (groupPos == 0) { // E-mail group
String[] PROJECTION = new String[] {
ContactsContract.RawContacts._ID,
ContactsContract.CommonDataKinds.Email.DATA };
String sortOrder = "CASE WHEN "
+ ContactsContract.Contacts.DISPLAY_NAME
+ " NOT LIKE '%#%' THEN 1 ELSE 2 END, "
+ ContactsContract.Contacts.DISPLAY_NAME + ", "
+ ContactsContract.CommonDataKinds.Email.DATA
+ " COLLATE NOCASE";
String selection = ContactsContract.CommonDataKinds.Email.DATA
+ " NOT LIKE ''";
cl = new CursorLoader(getApplicationContext(),
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
PROJECTION, selection, null, sortOrder);
} else if (groupPos == 1) { // Name group
Uri contactsUri = ContactsContract.Data.CONTENT_URI;
String selection = "(("
+ ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
+ " NOTNULL) AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.HAS_PHONE_NUMBER
+ "=1) AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
+ " != '') AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
+ " = '1' ))"; // Row ID 1 == All contacts
String sortOrder = ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
+ " COLLATE LOCALIZED ASC";
cl = new CursorLoader(getApplicationContext(), contactsUri,
CONTACTS_PROJECTION, selection, null, sortOrder);
}
} else {
// group cursor
Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
String selection = "((" + ContactsContract.Groups.TITLE
+ " NOTNULL) AND (" + ContactsContract.Groups.TITLE
+ " == 'Coworkers' ) OR ("
+ ContactsContract.Groups.TITLE
+ " == 'My Contacts' ))"; // Select only Coworkers
// (E-mail only) and My
// Contacts (Name only)
String sortOrder = ContactsContract.Groups.TITLE
+ " COLLATE LOCALIZED ASC";
cl = new CursorLoader(getApplicationContext(), groupsUri,
GROUPS_SUMMARY_PROJECTION, selection, null, sortOrder);
}
return cl;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in.
int id = loader.getId();
// Log.d("Dump Cursor MainActivity",
// DatabaseUtils.dumpCursorToString(data));
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 = listAdapter
.getGroupMap();
try {
int groupPos = groupMap.get(id);
Log.d(DEBUG_TAG, "onLoadFinished() for groupPos "
+ groupPos);
listAdapter.setChildrenCursor(groupPos, data);
} catch (NullPointerException e) {
Log.w("DEBUG",
"Adapter expired, try again on the next query: "
+ e.getMessage());
}
}
} else {
listAdapter.setGroupCursor(data);
}
}
#Override
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 {
listAdapter.setChildrenCursor(id, null);
} catch (NullPointerException e) {
Log.w(DEBUG_TAG,
"Adapter expired, try again on the next query: "
+ e.getMessage());
}
} else {
listAdapter.setGroupCursor(null);
}
}
};
private ContentObserver mSpeakerChangesObserver = new ContentObserver(
new Handler()) {
#Override
public void onChange(boolean selfChange) {
if (getApplicationContext() != null) {
runOnUiThread(new Runnable() {
public void run() {
getSupportLoaderManager().restartLoader(-1, null,
mSpeakersLoaderCallback);
}
});
}
}
};
}
MyListAdapter.java:
package com.example.cursortreeadaptersearch;
import java.util.HashMap;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorTreeAdapter;
import android.widget.TextView;
public class MyListAdapter extends CursorTreeAdapter {
public HashMap<String, View> childView = new HashMap<String, View>();
/**
* The columns we are interested in from the database
*/
private final String DEBUG_TAG = getClass().getSimpleName().toString();
protected final HashMap<Integer, Integer> mGroupMap;
private MainActivity mActivity;
private LayoutInflater mInflater;
String mConstraint;
public MyListAdapter(Cursor cursor, Context context) {
super(cursor, context);
mActivity = (MainActivity) context;
mInflater = LayoutInflater.from(context);
mGroupMap = new HashMap<Integer, Integer>();
}
#Override
public View newGroupView(Context context, Cursor cursor,
boolean isExpanded, ViewGroup parent) {
final View view = mInflater.inflate(R.layout.list_group, parent, false);
return view;
}
#Override
public void bindGroupView(View view, Context context, Cursor cursor,
boolean isExpanded) {
TextView lblListHeader = (TextView) view
.findViewById(R.id.lblListHeader);
if (lblListHeader != null) {
lblListHeader.setText(cursor.getString(cursor
.getColumnIndex(ContactsContract.Groups.TITLE)));
}
}
#Override
public View newChildView(Context context, Cursor cursor,
boolean isLastChild, ViewGroup parent) {
final View view = mInflater.inflate(R.layout.list_item, parent, false);
return view;
}
#Override
public void bindChildView(View view, Context context, Cursor cursor,
boolean isLastChild) {
TextView txtListChild = (TextView) view.findViewById(R.id.lblListItem);
if (txtListChild != null) {
txtListChild.setText(cursor.getString(1)); // Selects E-mail or
// Display Name
}
}
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.getSupportLoaderManager().getLoader(groupId);
if (loader != null && !loader.isReset()) {
mActivity.getSupportLoaderManager().restartLoader(groupId, null,
mActivity.mSpeakersLoaderCallback);
} else {
mActivity.getSupportLoaderManager().initLoader(groupId, null,
mActivity.mSpeakersLoaderCallback);
}
return null;
}
// Access method
public HashMap<Integer, Integer> getGroupMap() {
return mGroupMap;
}
public void filterList(CharSequence constraint) {
// TODO Filter the data here
}
}
I have very considerably simplified and cleaned the code (so that you guys that not need to do).
As you can see, I've in total 3 cursors (1 for the groups and 2 for the children). The data is get from ContactsContract (which are the contacts of the user).
The cursor from child 1 represents all the e-mails of all contacts and the cursor from child 2 represents all the display names of the contacts. (The most of the loader functions is from here).
The only thing is now how do I implement a search? Should I do it trough Content Provider or a raw query in the database? I would like that the results of both children tables is displayed. I think because it's easy to make a fault while typing that tokenize=porter is a option in my case.
I hope that someone can point me in a good direction.
Edit:
I've tried this in MyListAdapter.java (with FilterQueryProvider as suggested by Kyle I.):
public void filterList(CharSequence constraint) {
final Cursor oldCursor = getCursor();
setFilterQueryProvider(filterQueryProvider);
getFilter().filter(constraint, new FilterListener() {
public void onFilterComplete(int count) {
// assuming your activity manages the Cursor
// (which is a recommended way)
notifyDataSetChanged();
// stopManagingCursor(oldCursor);
// final Cursor newCursor = getCursor();
// startManagingCursor(newCursor);
// // safely close the oldCursor
if (oldCursor != null && !oldCursor.isClosed()) {
oldCursor.close();
}
}
});
}
private FilterQueryProvider filterQueryProvider = new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
// assuming you have your custom DBHelper instance
// ready to execute the DB request
String s = '%' + constraint.toString() + '%';
return mActivity.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
MainActivity.CONTACTS_PROJECTION,
ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME + " LIKE ?",
new String[] { s },
null);
}
};
And this in MainActivity.java:
search.setOnQueryTextListener(new OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
listAdapter.filterList(query);
expandAll();
return false;
}
#Override
public boolean onQueryTextChange(String query) {
listAdapter.filterList(query);
expandAll();
return false;
}
});
search.setOnCloseListener(new OnCloseListener() {
#Override
public boolean onClose() {
listAdapter.filterList("");
expandAll();
return false;
}
});
But then I get these errors when I try to search:
12-20 13:20:19.449: E/CursorWindow(28747): Failed to read row 0, column -1 from a CursorWindow which has 96 rows, 4 columns.
12-20 13:20:19.449: D/AndroidRuntime(28747): Shutting down VM
12-20 13:20:19.449: W/dalvikvm(28747): threadid=1: thread exiting with uncaught exception (group=0x415c62a0)
12-20 13:20:19.499: E/AndroidRuntime(28747): FATAL EXCEPTION: main
12-20 13:20:19.499: E/AndroidRuntime(28747): java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
What I'm doing wrong? Or is this because I'm only return 1 query (display names) instead of 2 (display names and e-mails) in runQuery?
Edit 2:
First of all I've changed all my database implementations to ContactsContract. This is become easier to maintain so that you don't have to write your own database implementation.
What I now have tried is to save my constraint in runQuery() of FilterQueryProvider, and then in getChildrenCursor run a query against that constraint. (as suggested by JRaymond)
private String mConstraint;
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);
Bundle b = new Bundle();
b.putString("constraint", mConstraint);
Loader loader = mActivity.getSupportLoaderManager().getLoader(groupId);
if (loader != null && !loader.isReset()) {
if (mConstraint == null || mConstraint.isEmpty()) {
// Normal query
mActivity.getSupportLoaderManager().restartLoader(groupId,
null, mActivity.mSpeakersLoaderCallback);
} else {
// Constrained query
mActivity.getSupportLoaderManager().restartLoader(groupId, b,
mActivity.mSpeakersLoaderCallback);
}
} else {
if (mConstraint == null || mConstraint.isEmpty()) {
// Normal query
mActivity.getSupportLoaderManager().initLoader(groupId, null,
mActivity.mSpeakersLoaderCallback);
} else {
// Constrained query
mActivity.getSupportLoaderManager().initLoader(groupId, b,
mActivity.mSpeakersLoaderCallback);
}
}
return null;
}
And here is the FilterQueryProvider:
private FilterQueryProvider filterQueryProvider = new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
// Load the group cursor here and assign mConstraint
mConstraint = constraint.toString();
Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
String selection = "((" + ContactsContract.Groups.TITLE
+ " NOTNULL) AND (" + ContactsContract.Groups.TITLE
+ " == 'Coworkers' ) OR (" + ContactsContract.Groups.TITLE
+ " == 'My Contacts' ))"; // Select only Coworkers
// (E-mail only) and My
// Contacts (Name only)
String sortOrder = ContactsContract.Groups.TITLE
+ " COLLATE LOCALIZED ASC";
return mActivity.getContentResolver().query(groupsUri,
MainActivity.GROUPS_SUMMARY_PROJECTION, selection, null,
sortOrder);
}
};
As you can see I've load the query of the groups in order to get the getChildrenCursor working. Only what for query should I run in MainActivity that I get from the bundle?
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.
What you should to do is extend FilterQueryProvider. This provides a runQuery() function that returns a new cursor of filtered results (likely accomplished with a database query).
In your CursorTreeAdapter adapter implementation you will then use the setFilterQueryProvider() method to provide it an instance of your FilterQueryProvider.
Finally, when you want to perform filtering you will call mAdapter.getFilter().filter("c").
However seeing as you are not actually using the SearchView autocomplete features and instead populating your own list, your chosen solution is quite a bit more complicated than it needs to be. Why don't you instead drop the Content Provider and CursorTreeAdapter and use a more simple in-memory scheme of lists or maps to back your adapter? Populate the in-memory data as required (can your entire dataset fit in memory?).

ExpandableListView and SimpleCursorTreeAdapter with an SQLiteCursorLoader, getChildrenCursor not called

I'll start by saying i've researched this specific solution to death and am aware of the solutions "this has been solved HERE" and HERE, but I am unable to get these working. Other info: I'm using the compatibility library and also actionbarsherlock, which both have been working great, no issues there i dont think.
I haven an SQLite database that i want to fill my entries into expandable list grouped by date. I know that my database is correctly populated and here are the columns I have verified to contain proper data:
ID (1, 2, 3, 4, etc)
TITLE (some text)
URL (url as string)
DATE (ddMMyyyy)
where the date column data is like this: 22Dec2012 or 23Dec2012 etc.
There are many entries per date, i want to group those as children with the date as group. I see the database is filled with the correct info. I have a few problems, the main is that my getChildrenCursor never gets called, only the constructor gets called. Second is that per the linked examples, I cannot get the getSherlockActivity context into my adapter (nor does getActivity() work either, just for testing purposes). I'll explain the error with logcat below.
My Fragment
public class MyFragment extends SherlockFragment implements LoaderManager.LoaderCallbacks, OnItemClickListener {
public static MyFragment newInstance(String symbol) {
MyFragment f = new MyFragment();
Bundle args = new Bundle();
args.putString(Consts.INFO, info);
f.setArguments(args);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View myView = inflater.inflate(R.layout.fragment_layout3, container, false);
mListView = (ExpandableListView)myView.findViewById(R.id.expandableListView);
mListView.setOnItemClickListener(this);
return myView;
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onResume() {
super.onResume();
refresh();
}
public void refresh() {
Log.v(TAG, "refresh");
populateExpandableList();
loader = getLoaderManager().getLoader(-1);
if (loader != null && !loader.isReset()) {
getLoaderManager().restartLoader(-1, null, this);
} else {
getLoaderManager().initLoader(-1, null, this);
}
if (getLoaderManager().getLoader(0x9999) == null) {
getLoaderManager().initLoader(0x9999, null, this);
//getLoaderManager().initLoader(-1, null, this);
}
else {
getLoaderManager().restartLoader(0x9999, null, this);
//getLoaderManager().restartLoader(-1, null, this);
}
getLoaderManager().getLoader(0x9999).forceLoad();
//getLoaderManager().getLoader(1).forceLoad();
}
private void populateExpandableList() {
// Set up our adapter
Log.v(TAG, "Endtering setup adapter");
mDbHelper = new DBHelper(getSherlockActivity(), DBConstants.DATABASE_NAME, null, DBConstants.DATABASE_VERSION);
mAdapter = new MyExpandableAdapter(getSherlockActivity(), this,
R.layout.news_list_group,
R.layout.news_list_child,
new String[] { DBConstants.DATE_GROUP_NAME }, // Name for group layouts
new int[] { R.id.group_title },
new String[] { DBConstants.TITLE_NAME, DBConstants.URL_NAME }, // Name for child layouts
new int[] { R.id.item_title, R.id.item_value });
mListView.setAdapter(mAdapter);
}
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
// TODO Auto-generated method stub
}
#Override
public Loader onCreateLoader(int loaderId, Bundle args) {
topics = new ArrayList<String>();
if (loaderId == 0x9999) {
return new MyLoader(getSherlockActivity(), mHandler, topics.toArray(new String[topics.size()]));
}
if (loaderId != -1 && loaderId != 0x9999) {
Log.v(TAG, "CreateLoader child cursor: loader ID: "+loaderId);
// child cursor
mCursorLoader = new SQLiteCursorLoader(getSherlockActivity(), mDbHelper, "SELECT _ID, "+DBConstants.TITLE+", " +DBConstants.URL + ", " + DBConstants.TIME + ", " + DBConstants.STATUS + " FROM "+DBConstants.TABLE+" WHERE "+ DBConstants.KEY_ID+"="+String.valueOf(loaderId)+" ORDER BY "+DBConstants.TIME+" DESC", null);
return mCursorLoader;
}
else if (loaderId != 0x9999) {
Log.v(TAG, "CreateLoader group cursor: loader ID: "+loaderId);
// group cursor
mCursorLoader = new SQLiteCursorLoader(getSherlockActivity(), mDbHelper, "SELECT date, "+DBConstants.TITLE+", "+DBConstants.URL+" FROM "+DBConstants.TABLE, null);
return mCursorLoader;
}
return null;
}
#Override
public void onLoadFinished(Loader loader, Object result) {
Log.v(TAG, "onLoadFinished");
if(result != null /*&& result.length > 0*/) {
if (loader.getId() == 0x9999) {
getLoaderManager().restartLoader(-1, null, this);
getLoaderManager().getLoader(-1).forceLoad();
}
if (loader.getId() != -1 && loader.getId() != 0x9999) {
Log.v(TAG, "LoadFinished second loader: loaderID: "+loader.getId());
if (!((Cursor) result).isClosed()) {
Log.v(TAG, "data.getCount() " + ((Cursor) result).getCount());
HashMap<Integer,Integer> groupMap = mAdapter.getGroupMap();
try {
int groupPos = groupMap.get(loader.getId());
Log.v(TAG, "onLoadFinished() for groupPos " + groupPos);
mAdapter.setChildrenCursor(groupPos, (Cursor) result);
} catch (NullPointerException e) {
Log.w("DEBUG","Adapter expired, try again on the next query: "
+ e.getMessage());
}
}
else {
mAdapter.setGroupCursor((Cursor) result);
}
}
else {
}
}
}
#Override
public void onLoaderReset(Loader loader) {
int id = loader.getId();
Log.v(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);
}
}
public class MyExpandableAdapter extends SimpleCursorTreeAdapter {
private final String TAG = getClass().getSimpleName().toString();
private MyFragment mFragment;
protected HashMap<Integer, Integer> mGroupMap = null;
public NewsFeedExpandableAdapter(Context context, MyFragment mf, int groupLayout, int childLayout, String[] groupFrom,
int[] groupTo, String[] childrenFrom, int[] childrenTo) {
super(context, null, groupLayout, groupFrom, groupTo, childLayout, childrenFrom, childrenTo);
mFragment = mf;
mGroupMap = new HashMap<Integer, Integer>();
Log.v(TAG, "Adapter constructor");
}
#Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
// Given the group, we return a cursor for all the children within that group
Log.v(TAG, "getChildrenCursor");
int groupPos = groupCursor.getPosition();
int groupId = groupCursor.getInt(groupCursor.getColumnIndex(DBConstants.TIME_GROUP_NAME));
Log.v(TAG, "getChildrenCursor() for groupPos " + groupPos);
Log.v(TAG, "getChildrenCursor() for groupId " + groupId);
mGroupMap.put(groupId, groupPos);
Loader loader = mFragment.getLoaderManager().getLoader(groupId);
if ( loader != null && !loader.isReset() ) {
mFragment.getLoaderManager().restartLoader(groupId, null, mFragment);
} else {
mFragment.getLoaderManager().initLoader(groupId, null, mFragment);
}
return null;
}
//Accessor method
public HashMap<Integer, Integer> getGroupMap() {
return mGroupMap;
}
}
}
Now in my adapter above, you see i'm using :
Loader loader = mFragment.getLoaderManager().getLoader(groupId);
if ( loader != null && !loader.isReset() ) {
mFragment.getLoaderManager().restartLoader(groupId, null, mFragment);
} else {
mFragment.getLoaderManager().initLoader(groupId, null, mFragment);
}
rather than this:
Loader loader = getSherlockActivity().getLoaderManager().getLoader(groupId);
if ( loader != null && !loader.isReset() ) {
getSherlockActivity().getLoaderManager().restartLoader(groupId, null, mFragment);
} else {
getSherlockActivity().getLoaderManager().initLoader(groupId, null, mFragment);
}
Because when i use getActivity or getSherlockActivity, the code gives an error on the first line above saying:
"Type mismatch, cannot convert from Loader<Object> to Loader"
and on the third and fifth line errors:
"The method restartLoader(int, Bundle, LoaderManager.LoaderCallbacks<D>) in the type LoaderManager is not applicable for the arguments (int, null, MyFragment)"
Does anyone possibly know why i cannot use getActivity here? It's driving me completely bonkers. So i attempt to use mFragment.getLoaderManager instead using the fragment i passed in to the adapter, but i dont know if this is even correct.
I placed breakpoints in the code and it shows that the adapter constructor is called, and that's it, getChildrenCursor never gets called ever. in the fragment breakpoints, it reaches "Create Group Loader id=-1" and thats it. in onLoadFinished only the first "onLoadFinished" tag get's shown in the log. It never makes it into the IF statements within onLoadFinished. When i check that the loadFinished result is not null in logcat, it shows as a cursor object being passed in to onLoadFinished, but something just isnt working right.
prior to trying expandable list, i was using these identical SQLiteCursorLoader's setup, just retrieving all columns, and they worked fine. I am absolutely admitting that maybe both my SELECT statements are not correct. but i've tried a ton of different select queries, and everytime i get same result, nothing. Can anyone PLEASE help me I'm desperate and going bonkers. Thanks.

Categories