ContentProvider for multiple databases/ContentProviders - android

Can anyone tell how can I create a ContentProvider which can query multiple database/ContentProviders for search suggestions provided by SearchView.

With ContentProviders, you are querying for data using a ContentUrl which would look something like this
content://<authority>/<data_type>/<id>
authority is the content provider name, e.g. contacts or for custom one will be com.xxxxx.yyy.
data_type and id are to specify what data you need from the provide and, if needed, a specific value for the key.
So, if you are building your custom content provider you need to parse the content uri which you get as a parameter in the query function and decide what data you need to return as Cursor. UriMatcher class is very good choice for this case. Here is an example
static final String URL = "content://com.mycompany.myapp/students";
static final Uri CONTENT_URI = Uri.parse(URL);
static final UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.mycompany.myapp", "students", 1);
uriMatcher.addURI("com.mycompany.myapp", "students/#", 2);
}
then in your query function, you would have something like this:
switch (uriMatcher.match(uri)) {
case 1:
// we are querying for all students
// return a cursor all students e.g. "SELECT * FROM students"
break;
case 2:
// we are querying for all students
// return a cursor for the student matching the given id (the last portion of uri)
// e.g. "SELECT * FROM students WHERE _id = n"
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
I hope this answers your question and direct you to the right track.
You can see a good article with full example about how to use them, here
http://www.tutorialspoint.com/android/android_content_providers.htm

Related

How to handle advanced Uri Patterns in Android

Implementation of Content Provider with a complicated queries required a complex uri patterns, and i don't understand how can i handle this.
Can i use Regular Expression in my patterns? example: /user/:[a-zA-Z]/timeline/ if the user must contain only letters.
Which symbols tells the UriMather what the parameters example: /user/:userId/timeline/year/:yearNumber, i would get userId and yearNumber as parameters , so how do i should get the values ? should i use getPathSegments() and get the parameters manually?
or if i used /user/#/timeline/year/# how do i extract the # values
By using the format /user/#/timeline/year/# you can call:
uri.getPathSegments().get(1); // To get the first #
uri.getPathSegments().get(4); // to get the second #
// 0 -> user, 1 -> first #, 2 -> timeline, 3 -> year, 4 -> second #
So in your content provider you would have something like this:
private static final int USER_TIMELINE_YEAR = 1;
// ...
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
uriMatcher.addURI(PROVIDER_NAME, "user/#/timeline/year/#", USER_TIMELINE_YEAR);
}
// Usually a ContentProvider method like query, insert, delete and update
public void someMethod(Uri uri) {
if(uriMatcher.match(uri) == USER_TIMELINE_YEAR) {
String userId = uri.getPathSegments().get(1);
String timelineYear = uri.getPathSegments().get(4);
}
}
Check https://developer.android.com/guide/topics/providers/content-provider-creating.html and https://developer.android.com/reference/android/content/UriMatcher.html

Android: performance cost of content provider queries within queries

Writing this on the fly, so I apologize for the code sample. This is NOT real code, it's something I wrote in a plain text editor on the fly. No compile checking, couldn't remember all the exact class and method names, etc. It's just a written concept of what I'm trying to do, I'm looking for feedback on the broader concepts.
I'm working on retrieving a list of contacts from the content provider. I want to be able to filter the results based on the contact's account name. the user will be presented with all available accounts, and will select which ones are to be used, and then that will be used in the retrieval method.
The thing is, the account name is in RawContacts, and the rest of the info I want (display name, lookupID) is in Contacts. I know that ContactsContract.Contacts.Entity is the shortcut to access all of this, so this code sample is what I'm planning to do.
Again, this is written on the fly with no IDE or looking up methods or anything. I'm sure my syntax is bad in many places, but this shows the concept I'm trying to do.
private static final URI URI = ContactsContract.Contacts.URI;
private static final String[] FIRST_PROJECTION = new String[]{
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.LOOKUP_KEY
};
private String[] acceptedAccountNames = {Accepted Account Names Will Go Here (dynamic)};
private static final String[] SECOND_PROJECTION = new String[]{
ContactsContract.Contacts.Entity.ACCOUNT_NAME //This is whatever the entity -> RawContacts field name would be
};
public List<Contact> loadContacts(Context context){
List<Contact> contacts = new ArrayList<>();
ContentProvider provider = context.getContentProvider();
Cursor contactsCursor = provider.query(URI, FIRST_PROJECTION, null, null);
contactsCursor.movetoFirst();
while(!contactsCursor.isAtLast()){
String name = contactsCursor.getString(contactsCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
long lookupKey = contactsCursor.getLong(contactsCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
Uri idUri = Uri.makeWithId(URI, lookupKey);
Uri entityUri = Uri.makeWithTableName(idUri, "entity");
Cursor contactEntityCursor = provider.query(entityUri, SECOND_PROJECTION, null, null);
contactEntityCursor.moveToFirst();
String accountName = contactEntityCursor.getString(contactEntityCursor.getColumnIndex(ContactsContract.Contacts.Entity.ACCOUNT_NAME));
if(Arrays.asList(acceptedAccountNames).contains(accountName)){
Contact contact = new Contact(lookupKey, name);
contacts.add(contact);
}
contactsCursor.moveToNext();
}
return contacts;
}
As you can see, I create a cursor while looping over another cursor. I'm essentially creating a new cursor for each contact in the list.
My question is twofold:
1) What would be the performance implications of this? With a large enough list, would this severely hurt app performance?
2) Is there a better way to do this? As in, a way to do this in a single query, getting all the data I'm looking for in the cursor.
Thanks so much in advance.

Get Android contacts with type-to-filter functionality, restricted to a specific account

I'm trying to:
Display a list of contacts
Let the user search through them by typing a query
Limit search results only to a specific Google/Gmail account.
This is how I build the URI for the cursor:
// User is searching for 'jo'
String query = "jo";
Uri uri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(query));
// Restrict the query to contacts from 'example#gmail.com'
Uri.Builder builder = uri.buildUpon();
builder.appendQueryParameter(
ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(ContactsContract.Directory.DEFAULT));
builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, "example#gmail.com");
builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, "com.google");
uri = builder.build();
This is the final URI:
content://com.android.contacts/contacts/filter/jo?directory=0&account_name=example%40gmail.com&account_type=com.google
Currently, this shows search results from all accounts on the phone.
NOTE: If I use Contacts.CONTENT_URI instead of Contacts.CONTENT_FILTER_URI, then specifying the directory/account works as expected, but I can no longer use 'type-to-filter' style search.
The documentation does state:
The most important use case for Directories is search. A Directory
provider is expected to support at least Contacts.CONTENT_FILTER_URI.
Could anyone help point out what I might be doing wrong?
I added your code in Google's example for contact retrieving, and with a couple of changes it worked perfectly with my Google for Work account.
The changes I made were:
remove the line with DIRECTORY_PARAM_KEY, as I didn't find it to make any difference
removed ContactsQuery.SELECTION from the return statement, because that constant prevents "invisible" contacts from being displayed.
The changes were made to ContactsListFragment.java
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// If this is the loader for finding contacts in the Contacts Provider
// (the only one supported)
if (id == ContactsQuery.QUERY_ID) {
Uri contentUri;
// There are two types of searches, one which displays all contacts and
// one which filters contacts by a search query. If mSearchTerm is set
// then a search query has been entered and the latter should be used.
if (mSearchTerm == null) {
// Since there's no search string, use the content URI that searches the entire
// Contacts table
contentUri = ContactsQuery.CONTENT_URI;
} else {
// Since there's a search string, use the special content Uri that searches the
// Contacts table. The URI consists of a base Uri and the search string.
contentUri = Uri.withAppendedPath(ContactsQuery.FILTER_URI, Uri.encode(mSearchTerm));
}
// HERE COMES YOUR CODE (except the DIRECTORY_PARAM_KEY line)
Uri.Builder builder = contentUri.buildUpon();
builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, "example#mycompany.com");
builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, "com.google");
contentUri = builder.build();
// Returns a new CursorLoader for querying the Contacts table. No arguments are used
// for the selection clause. The search string is either encoded onto the content URI,
// or no contacts search string is used. The other search criteria are constants. See
// the ContactsQuery interface.
return new CursorLoader(getActivity(),
contentUri,
ContactsQuery.PROJECTION,
null, // I REMOVED SELECTION HERE
null,
ContactsQuery.SORT_ORDER);
}
Log.e(TAG, "onCreateLoader - incorrect ID provided (" + id + ")");
return null;
}

ContentResolver wrong MIME type

I am writing a unit test in my Android app to test a ContentProvider. This test extends ProviderTestCase2. I have the following code:
// Tests the MIME type for the recent_searches table URI.
String mimeType = mMockResolver.getType(SearchEntryProvider.CONTENT_URI);
assertEquals(SearchEntryProvider.CONTENT_TYPE, mimeType);
The mock ContentResolver sees the value of SearchEntryProvider.CONTENT_URI as:
url = {android.net.Uri$StringUri#831696969096}"content://com.eazyigz.provider.RussiaMediaSearch/searches"
The problem is that this assertEquals fails because it expects a CONTENT_TYPE of
vnd.android.cursor.dir
but instead receives
vnd.android.cursor.item
For the life of me, I cannot figure out how to get the mimeType to be vnd.android.cursor.dir. Anybody have experience with this?
Thanks,
Igor
For anyone who is interested, the problem was in my ContentProvider's implementation of getType method. I had to implement it like this to return the correct CONTENT_TYPE:
#Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
// If the pattern is for searches, returns the general content type.
case SEARCHES:
return CONTENT_TYPE;
case SEARCH_ID:
return CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException(INVALID_URI + uri);
}
}

How to handle unsupported column in custom content provider

I have a custom content provider for sharing files. For columns (via query method) i support only OpenableColumns.DISPLAY_NAME and OpenableColumns.SIZE.
While testing i noticed that Photos app (Google+) have problems (NullPointerException) opening images through my content provider, because they are retrieving _data column, which is not supported in my content provider, and for it i return null (for column names and values).
Later i updated this part of code, so now it returns in columns array _data column, but it's value as null.
It seems that this works now ok in Photos app, but i'm curious... Does anybody know if this is in general specified/standardized, what we should return in case of unsupported column?
Part of confusion is: should we on side of content provider handle such scenarios as i did, or should apps that uses content providers do additional checking? Since some apps can check if column exists, then do something with value (but value will be null). On other side, other apps can expect column, and assume if column exists, then value should also exists.
I know that the best approach is to cover all situations, but initial question remains: is such handling specified somewhere in the documentation?
Here is also section of code for better imagination:
String cols[] = new String[projection.length];
Object vals[] = new Object[projection.length];
for(int i=0; i<projection.length; i++){
//projection is passed to query() by 3rd party app
String column = projection[i];
if ( OpenableColumns.DISPLAY_NAME.equals(column) ) {
cols[i] = OpenableColumns.DISPLAY_NAME;
vals[i] = name;
}else if ( OpenableColumns.SIZE.equals(column) ) {
cols[i] = OpenableColumns.SIZE;
vals[i] = file.length();
}else{
//THIS PART OF CODE WAS LATER ADDED
//for unsupported column return column name
//but it's value will be null
cols[i] = column;
vals[i] = null;
}
}
MatrixCursor mc = new MatrixCursor(cols, 1);
mc.addRow(vals);

Categories

Resources