I am using a a CursorLoader and ContentProvider to read data from SQLite DB. The 'drugs' table has about 300,000 rows. I have created index on 'name' column in the 'drugs' table.
I have a SearchFragment where I search for drugs based on user query as follows:
public void update(String query, SearchView mSearchView, String mSearchType) {
mQuery = query;
Bundle b = new Bundle();
b.putString("query", mQuery);
mLoaderManager.restartLoader(Constants.DRUG_LOADER, b, this);
Log.d(TAG, "Searching for " + mQuery);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Log.d(TAG, "onCreateLoader");
return new CursorLoader(getActivity(), DrugsProvider.DRUGS_URI,
null,
ReapDbContract.Drugs.NAME + " like ?",
new String[]{args.getString("query") + "%"},
null
);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
Log.d(TAG, "onLoadFinished");
mAdapter.setCursor(c);
mAdapter.notifyDataSetChanged();
Log.d(TAG, "Done query for " + mQuery);
}
This is relevant part of DrugsProvider which runs the query:
#Override
public Cursor query(Uri uri, String[] projection, String where, String[] values, String sortOrder) {
switch (sUriMatcher.match(uri)) {
case DRUGS_TABLE:
Cursor c = mDbHelper.query(Drugs.TABLE_NAME, projection, where, values, sortOrder);
Log.d(TAG, "query done");
return c;
case DRUG_ROW:
String id = uri.getLastPathSegment();
return mDbHelper.getRow(Drugs.TABLE_NAME, id);
default:
throw new IllegalArgumentException("Invalid URI: " + uri);
}
}
The DrugsHelper actually implements the query:
public Cursor query(String tableName, String[] projection, String where, String[] values, String sortOrder) {
Log.d(TAG, "query: start: " + Arrays.toString(values));
SQLiteDatabase db = getWritableDatabase();
Log.d(TAG, "query: db");
Cursor c = db.query(tableName, projection, where, values, null, null, sortOrder);
Log.d(TAG, "query: done: " + Arrays.toString(values));
return c;
}
The debug logs clearly tell that the actual DB query gets executed in a few milliseconds, but there is a delay of almost 500ms between the end of query and call to onLoadFinished. This is making the search extremely sluggish.
Sometimes there is also a delay of about 500ms between call to onCreateLoader and when the provider actually runs the query. Here is a sample log:
================================================================
01-07 10:23:13.618 29288-29288/in.workcell.pos D/SearchFragment: onCreateLoader
01-07 10:23:13.627 29288-29288/in.workcell.pos D/SearchFragment: Searching for pari
01-07 10:23:13.632 29288-29524/in.workcell.pos D/DrugsHelper: query: start: [pari%]
01-07 10:23:13.632 29288-29524/in.workcell.pos D/DrugsHelper: query: db
01-07 10:23:13.632 29288-29524/in.workcell.pos D/DrugsHelper: query: done: [pari%]
01-07 10:23:13.632 29288-29524/in.workcell.pos D/DrugsProvider: query done
01-07 10:23:14.098 29288-29288/in.workcell.pos D/SearchFragment: onLoadFinished
01-07 10:23:14.098 29288-29288/in.workcell.pos D/SearchFragment: Done query for pari
================================================================
The DrugsProvider was done at 10:23:13.632, but onLoadFinished got called at 10:23:14.098, delay of 466ms! How can I debug what is causing this delay ?
Related
I am trying to retrieve the distinct values from my database using my contentprovider query and CursorLoader. While the CursorLoader does not allow a distinct specification, I discovered the setDistinct method that can be added to a querybuilder for adding this specification. I am not retrieving the desired result and curious as to why. My query looks like below
#Override
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
// create SQLiteQueryBuilder for querying flower table
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(Flower.TABLE_NAME);
queryBuilder.setDistinct(true);
switch (uriMatcher.match(uri)) {
case OneFlower: // contact with specified id will be selected
queryBuilder.appendWhere(
Flower._ID + "=" + uri.getLastPathSegment());
break;
case CONTACTS: // all contacts will be selected
break;
default:
throw new UnsupportedOperationException(
getContext().getString(R.string.invalid_query_uri) + uri);
}
Cursor cursor = queryBuilder.query(dbHelper.getReadableDatabase(),
projection, selection, selectionArgs, null, null, sortOrder);
// configure to watch for content changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
while my CursorLoader looks like this
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID:
return new CursorLoader(
getContext(),
DatabaseDescription.Flower.CONTENT_URI,
FROM_COLUMNS,
COLUMN_LOCATION + "<> ''",
null,
COLUMN_LOCATION + " ASC"
);
default:
if (BuildConfig.DEBUG)
throw new IllegalArgumentException("no id handled!");
return null;
}
}
I have a sqlite database with "label" and "idea" tables in my Android app. made a foreign key on idea table as idea_label with int values that connected to label_table on its _id.
I use Loader to load my Cursor on the mainActivity that loads my idea table from the provider. As obvious it load idea_label int (But what I seek is to load the value from label_table which sets in label_body).
My loader on mainActivity class
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {
DatabaseContract.IdeaEntry._ID,
DatabaseContract.IdeaEntry.COLUMN_IDEA_NAME,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DESCRIPTION,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DATE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ICON,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ACTIVE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_FAVORITE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_DONE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ARCHIVED,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER,
};
return new CursorLoader(this,
DatabaseContract.IdeaEntry.CONTENT_URI_IDEA,
projection,
null, // selection
null, // selectionArgs
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER // order
);
}
That calls this section on my provider class
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case IDEAS:
cursor = database.query(DatabaseContract.IdeaEntry.IDEA_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
Is there a way? trigger another loader? or my implementation is wrong in this case? any way or help that direct me to the right direction, will be very appreciated.
With SQLiteQueryBuilder you can join tables in setTables method and then just specify label_body column of label table in projection.
String[] projection = {
DatabaseContract.IdeaEntry._ID,
....
DatabaseContract.LabelEntry.COLUMN_LABEL_BODY,
};
...
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables("IDEA JOIN LABEL ON IDEA.IDEA_LABEL = LABEL._ID");
builder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
Thanks to #gar_r to help and guide me , I did like this with DatabaseContract constants:
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {
DatabaseContract.IdeaEntry.IDEA_TABLE_NAME + "." + DatabaseContract.IdeaEntry._ID,
DatabaseContract.IdeaEntry.COLUMN_IDEA_NAME,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DESCRIPTION,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DATE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ICON,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ACTIVE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_FAVORITE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_DONE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ARCHIVED,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER,
DatabaseContract.LabelEntry.COLUMN_LABEL_BODY,
};
return new CursorLoader(this,
DatabaseContract.IdeaEntry.CONTENT_URI_IDEA,
projection,
null, // selection
null, // selectionArgs
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER // order
);
}
That calls this section on my provider class
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case IDEAS:
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(DatabaseContract.IdeaEntry.IDEA_TABLE_NAME + " JOIN " + DatabaseContract.LabelEntry.LABEL_TABLE_NAME
+ " ON " + DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL + " = " + DatabaseContract.LabelEntry.LABEL_TABLE_NAME + "." + DatabaseContract.LabelEntry._ID);
cursor = builder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
break;
I am using Loader to get data from db and I have some trouble whit distinct, in simple words - distinct doesn't work. here is my code:
private String[] CONTACTS_COLUMNS ={
"DISTINCT " + ContactsEntry.CONTACT_ID + " AS _id",
ContactsEntry.CONTACT_FROM,
ContactsEntry.CONTACT_NAME,
CardsPhonesEntry.CONTACT_PHONE,
CardsPhonesEntry.CARD_NUMBER
};
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(),
ContactsEntry.CONTENT_URI,
CONTACTS_COLUMNS,
mSelection, null, null);
}
here is some code from my contentProvider:
#Nullable
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor retCursor;
int match = sUriMatcher.match(uri);
switch (match){
case PEOPLE:
retCursor = getAllContacts(projection, selection, selectionArgs, sortOrder);
break;
default:
throw new UnsupportedOperationException("Unknown uri in query(): " + uri);
}
return retCursor;
}
private Cursor getAllContacts(String[] projection, String selection, String[] selectionArgs,
String sortOrder){
return sPeopleWithCardsAndPhones.query(
mDbHelper.getReadableDatabase(),
projection,
selection,
selectionArgs,
null, null,
sortOrder
);
}
static {
sPeopleWithCardsAndPhones = new SQLiteQueryBuilder();
sPeopleWithCardsAndPhones.setTables(
ContactsEntry.TABLE_NAME + " LEFT JOIN " + CardsPhonesEntry.TABLE_NAME + " ON " +
ContactsEntry.TABLE_NAME + "." + ContactsEntry.CONTACT_ID + " = " +
CardsPhonesEntry.TABLE_NAME + "." + CardsPhonesEntry.OWNER_ID
);
}
what i am doing wrong and why distinct doesn't want to work?
EDIT:
For example: I have 1 contact in first table, and in the second table there are 2 cards linked to this contact by contact_id;
I need to show list with only 1 item (first contact) without depending how much cards it has. and now when init loader - it shows me this contact twice, but not once.
I have looked at this for a couple of days now and I completely can't work out why my content provider return 0 using the arguments I am passing it.
Here's my contentResolver code:
String[] expenditureProjection = {
BusinessOpsDatabase.COL_EXPEND_CAT_ID,
BusinessOpsDatabase.COL_EXPEND_DATE,
BusinessOpsDatabase.COL_EXPEND_AMOUNT,
BusinessOpsDatabase.COL_EXPEND_DESC,
BusinessOpsDatabase.COL_STERLING_EXCHANGE,
BusinessOpsDatabase.COL_COMPANY_ID,
BusinessOpsDatabase.CURRENCY_ID,
BusinessOpsDatabase.COL_MOD_DATE
};
// Defines a string to contain the selection clause
String selectionClause = null;
// An array to contain selection arguments
String[] selectionArgs = {expend_id.trim()};
selectionClause = BusinessOpsExpenditureProvider.EXPENDITURE_ID + "=?";
Log.d(TAG, expend_id+" Selected from list.");
Cursor expendCursor = getContentResolver().query(
BusinessOpsExpenditureProvider.CONTENT_URI, expenditureProjection, selectionClause, selectionArgs, null);
if (null == expendCursor) {
Log.d(TAG, "Expenditure cursor: Is null");
} else if (expendCursor.getCount() < 1) {
Log.d(TAG,"Expenditure cursor: Search was unsuccessful: "+expendCursor.getCount());
} else {
Log.d(TAG,"Expenditure cursor: Contains results");
int i=0;
expendCursor.moveToFirst();
// loop through cursor and populate country array
while (expendCursor.isAfterLast() == false)
{
expend_date_edit.setText(expendCursor.getString(1));
expend_amount_edit.setText(expendCursor.getString(3));
expend_desc_edit.setText(expendCursor.getString(4));
i++;
expendCursor.moveToNext();
}
}
Here's my content provider query method:
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mDB.getWritableDatabase();
// A convenience class to help build the query
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(BusinessOpsDatabase.TABLE_EXPENDITURE);
switch (sURIMatcher.match(uri)) {
case EXPENDITURE:
if(selection != null && selectionArgs != null){
//values.get("company_contact");
String segment = uri.getLastPathSegment();
Log.d(TAG, "Last path segment: "+ segment);
String whereClause = BusinessOpsDatabase.EXPENDITURE_ID + "="+ selectionArgs[0];
Log.d(TAG, "Where clause: "+whereClause);
}
break;
case EXPENDITURE_ID:
// If this is a request for an individual status, limit the result set to that ID
qb.appendWhere(BusinessOpsDatabase.EXPENDITURE_ID + "=" + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
// Query the underlying database
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, null);
// Notify the context's ContentResolver if the cursor result set changes
c.setNotificationUri(getContext().getContentResolver(), uri);
// Return the cursor to the result set
return c;
}
I'm printing the whereclause to the log and I see '_id=3' which should be fine because I have pulled off a copy of my SQLite database and I can see that the expenditure table has an _id 3 row in it. Any Ideas?
What an epic problem this has been. I found the error in my ContentResolver code.
selectionClause = BusinessOpsExpenditureProvider.EXPENDITURE_ID + "=?";
I was using the EXPENDITURE_ID variable from the provider rather than the database class. The line now reads.
selectionClause = BusinessOpsDatabase.EXPENDITURE_ID + "=?";
And works!
I'm using Loader and Content Provider to fill a listview with data from my database.
My problem is that, if I use the line below in my Content Provider to get data from database, the query and onLoadFinished is executed very fast (no problem to fill the listview):
Cursor c = this.mDb.rawQuery("SELECT * FROM " + DATABASE_TABLE , null);
However, If I change the line to :
Cursor c = this.mDb.rawQuery("SELECT DISTINCT D._id, D.Name, D.Icon FROM Plain A, Test B, Exercise C, Group D WHERE A.ID=1 AND B.PlainID=A._id AND B.TestID=C._id AND C.ExerciseID=D._id", null);
The query continues to run fast, but the application takes approximately 2s to call onLoadFinished and fill the listview.
why this delay if the cursor already executed query?
My Loader:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Log.d(TAG, "onCreateLoader");
Uri uri = ContentProviderGroup.CONTENT_URI;
String[] projection = { Group.KEY_ROW_ID, Group.KEY_NAME, Group.KEY_ICON };
CursorLoader cursorLoader = new CursorLoader(this, // Parent activity context
uri, // Table to query
projection, // Projection to return
null, // No selection clause
null, // No selection arguments
null); // Default sort order
Log.d(TAG, "END onCreateLoader");
return cursorLoader;
}
My Content Provider (query function):
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor;
if(uriMatcher.match(uri) == CODE){
Log.d(TAG, "CALL SELECT");
cursor = mCustomerDB.getGroup(selection, selectionArgs);
Log.d(TAG, "END SELECT");
return cursor;
}else{
return null;
}
}
My DBAdapter (getGroup function):
public Cursor getGroup(String selection, String[] selectionArgs){
Log.d(TAG, "SELECT INIT");
/////// HERE I USE the rawQuery /////////
Log.d(TAG, "SELECT END");
return c;
}
Thank You