How to implement a content provider with more than one table? - android

update: looking at "vnd.android.cursor.dir/vnd.google.note" and "vnd.android.cursor.item/vnd.google.note" it seemed to me as though the cursor was for one table.
From the examples it appears as though content provider were designed to work with one table. I do know how to use multiple tables in sqlite but it seems to me that the content provider seems to be about picking one row or multiple rows from one table.
see http://developer.android.com/guide/topics/providers/content-provider-creating.html
Also, see the notepad sample in adt-bundle-windows-x86-20131030\sdk\samples\android-19\legacy\NotePad\src\com\example\android\notepad
Suppose I want to have notes by topic.
I would like to have a Topics table with columns _id and Title_text.
I would like to have the Notes table with columns _id and foreign key Topic_id and Note_text.
How would one design the Topics and Notes?
But looking at the Notes sample, the content URIs and docs on content providers, it appears as though having multiple related tables is an afterthought and is not obvious to me.
from NotepadProvider.java, Notepad.java:
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.note";
/**
* The MIME type of a {#link #CONTENT_URI} sub-directory of a single
* note.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.note";
public static final Uri CONTENT_ID_URI_BASE
= Uri.parse(SCHEME + AUTHORITY + PATH_NOTE_ID);
/**
* The content URI match pattern for a single note, specified by its ID. Use this to match
* incoming URIs or to construct an Intent.
*/
public static final Uri CONTENT_ID_URI_PATTERN
= Uri.parse(SCHEME + AUTHORITY + PATH_NOTE_ID + "/#");
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
...
switch (sUriMatcher.match(uri)) {
// If the incoming URI is for notes, chooses the Notes projection
case NOTES:
qb.setProjectionMap(sNotesProjectionMap);
break;
/* If the incoming URI is for a single note identified by its ID, chooses the
* note ID projection, and appends "_ID = <noteID>" to the where clause, so that
* it selects that single note
*/
case NOTE_ID:
qb.setProjectionMap(sNotesProjectionMap);
qb.appendWhere(
NotePad.Notes._ID + // the name of the ID column
"=" +
// the position of the note ID itself in the incoming URI
uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION));
break;

When creating a ContentProvider, the expectation is that other apps are going to use your database, and with that I mean other people who know nothing about your database scheme. To make things easy for them, you create and document your URIs:
To access all the books
content://org.example.bookprovider/books
to access books by id
content://org.example.bookprovider/books/#
to access books by author name
content://org.example.bookprovider/books/author
Create as many URIs as you need, that’s up to you. This way the user of your Provider can very easily access your database info, and maybe that’s why you are getting the impression that the Provider is designed to work with one table databases, but no, internally is where the work is done.
In your ContentProvider subclass, you can use a UriMatcher to identify those different URIs that are going to be passed to your ContentProvider methods (query, insert, update, delete). If the data the Uri is requesting is stored in several tables, you can actually do the JOINs and GROUP BYs or whatever you need with SQLiteQueryBuilder , e.g.
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder mQueryBuilder = new SQLiteQueryBuilder();
. . .
String Joins = " t1 INNER JOIN table2 t2 ON t2._id = t1._id"
+ " INNER JOIN table3 t3 ON t3._id = t1._id";
switch (mUriMatcher.match(uri)) {
case DATA_COLLECTION_URI:
mQueryBuilder.setTables(YourDataContract.TABLE1_NAME + Joins);
mQueryBuilder.setProjectionMap(. . .);
break;
case SINGLE_DATA_URI:
mQueryBuilder.setTables(YourDataContract.TABLE1_NAME + Joins);
mQueryBuilder.setProjectionMap(. . .);
mQueryBuilder.appendWhere(Table1._ID + "=" + uri.getPathSegments().get(1));
break;
case . . .
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
. . .
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor c = mQueryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, orderBy);
return c;
}
Hope it helps.

Excuse me, but I don't understand your question.
ContentProvider is designed (a one of it's aims)to wrap access to your tabels. Design of database schema is up to you.
Generally, you need to:
Define your tables/ It should be made by execution of sql command in class which extends SQLiteOpenHelper
Define an uri for them
Define a logic for queries to this tables as it was made for NOTE_ID
Update
For JOIN operations SQLiteQueryBuilder is usually used. In setTables() you need to write names of tables with JOIN clause, e.g.
.setTables(NoteColumns.TABLENAME +
" LEFT OUTER JOIN " + TopicColumns.TABLENAME + " ON " +
NoteColumns.ID + " = " + TopicColumns.ID);

Here is my code for multiple table query in content provider with projectionMap
//HashMap for Projection
mGroupImageUri = new HashMap<>();
mGroupImageUri.put(RosterConstants.JID,RosterProvider.TABLE_ROSTER+"."+RosterConstants.JID);
mGroupImageUri.put(RosterConstants.USER_NAME,RosterProvider.TABLE_ROSTER+"."+RosterConstants.USER_NAME);
mGroupImageUri.put(ChatConstants.MESSAGE,"c."+ChatConstants.MESSAGE+ " AS "+ ChatConstants.MESSAGE);
mGroupImageUri.put(ChatConstants.SENDER,"c."+ChatConstants.SENDER+" AS "+ChatConstants.SENDER);
mGroupImageUri.put(ChatConstants.URL_LOCAL,"c."+ChatConstants.URL_LOCAL+" AS "+ChatConstants.URL_LOCAL);
//case for content type of uri
case IMAGE_URI:
qBuilder.setTables(RosterProvider.TABLE_ROSTER
+ " LEFT OUTER JOIN "+ TABLE_NAME + " c"
+ " ON c."+ ChatConstants.JID + "=" + RosterProvider.TABLE_ROSTER + "."+RosterConstants.JID);
qBuilder.setProjectionMap(mGroupImageUri);
break;
//ContentResolver query for Projection form, selection and selection args
String[] PROJECTION_FROM = new String[]{
RosterConstants.JID,
RosterConstants.USER_NAME,
ChatConstants.MESSAGE,
ChatConstants.SENDER,
ChatConstants.URL_LOCAL
};
String selection = RosterProvider.TABLE_ROSTER +"."+RosterConstants.JID+ "='" + jid + "' AND " + "c."+ChatConstants.FILE_TYPE+"="+ChatConstants.IMAGE;
String[] selectionArgu = null;
String order = "c."+ChatConstants.MESSAGE+" ASC";
Cursor cursor = mContentReolver.query(ChatProvider.CONTENT_URI_GROUP_IMAGE_URI,
PROJECTION_FROM,selection, null,order);
//#ChatProvider.CONTENT_URI_GROUP_IMAGE_URI = 'your content type uri'
//#TABLE_NAME = 'table1'
//#RosterProvider.TABLE_ROSTER ='table2'

Related

How can I join records with different CONTENT_ITEM_TYPE but that share a common field, namely RAW_CONTACT_ID?

I need a Cursor selecting contacts belonging to specific groups ordered by their family names (not display_names).
It is easy to get a cursor returning the contacts belonging to the requested groups, and another one returning the contacts sorted by family names.
However the family name belongs to DATA records with ContactsContract.Data.MIMETYPE = ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE while contacts belonging to specific groups are to be found in records with ContactsContract.Data.MIMETYPE = ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE.
How can I join records with different CONTENT_ITEM_TYPE but that share a common field, namely RAW_CONTACT_ID?
You said you need to select contacts, if so you shouldn't use RAW_CONTACT_ID, but instead CONTACT_ID to join your contacts data.
A single Contact may be an aggregate of multiple RawContacts and in that case I assume you want all the details of that single contacts as one row.
Now to get what you want, you can't use a Cursor to iterate through the contacts, instead you should load all the data you need to memory (e.g. HashMap) and run through that.
BTW, if you prefer to query over the Contacts/RawContacts tables instead of the Data table, you can utilise DISPLAY_NAME_ALTERNATIVE column to get your sort, see: https://developer.android.com/reference/android/provider/ContactsContract.ContactNameColumns.html#DISPLAY_NAME_ALTERNATIVE
Example code:
int selectedGroupId = 12345;
HashSet<Long> ids = new HashSet<>();
// get all CONTACT_IDs belonging to some GROUP_ID
String[] projection = new String[]{Data.CONTACT_ID};
String selection = Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE + "' AND " + GroupMembership.GROUP_ROW_ID + "=" + selectedGroupId;
Cursor c = getContentResolver().query(Data.CONTENT_URI, projection, selection, null, null);
while (c.moveToNext()) {
ids.add(c.getLong(0));
}
c.close();
String[] projection = new String[]{Data.DISPLAY_NAME, Data.MIMETYPE, Data.DATA1};
// you can add more MIMETYPES to the selection here to get phones, emails, etc. for each contact
String selection = Data.CONTACT_ID + " IN (" + TextUtils.join(",", ids) + ") AND " + Data.MIMETYPE + "='" + StructuredName.CONTENT_ITEM_TYPE + "'";
c = getContentResolver().query(Data.CONTENT_URI, projection, selection, null, StructuredName.FAMILY_NAME + " ASC");
DatabaseUtils.dumpCursor(c);
c.close();

cursor loader query contact detail can't correctly detect the changes

I reference this link android developer training to implement for the retrieving contact detail with selection criteria
/*
* Defines the selection clause. Search for a lookup key
* and the Email MIME type
*/
private static final String SELECTION =
Data.LOOKUP_KEY + " = ?" +
" AND " +
Data.MIMETYPE + " = " +
"'" + Email.CONTENT_ITEM_TYPE + "'";
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
// Choose the proper action
switch (loaderId) {
case DETAILS_QUERY_ID:
// Assigns the selection parameter
mSelectionArgs[0] = mLookupKey;
// Starts the query
CursorLoader mLoader =
new CursorLoader(
getActivity(),
Data.CONTENT_URI,
PROJECTION,
SELECTION,
mSelectionArgs,
SORT_ORDER
);
...
}
When goes into change the contact name for example. Then in one case when adding contact is made by our application, cursor loader can't correctly detect the changes. I check the lookup key, that added contact key is different with others (for example : Orod-1340xxxxxxxx).
After searching the discussion, lookup key may change and suggest to use contact lookup uri with the lookup key. But the lookup uri cannot be used in above query. I need to query in Data table for the detail info.
How can i achieve that?
Thanks a lot.
Try using CONTACT_ID instead of LOOKUP_KEY.
private static final String SELECTION =
Data.CONTACT_ID + " = ? AND " +
Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";
You might stumble upon warnings not to use contact-ids and instead use lookup keys or lookup uris, but that's for persisting a contact reference into a database for long keeping, if you're app is currently up and running, and just recently queried for this contact-id, it's perfectly safe and ok to use it, it's even better to use contact-ids for this purpose since it's more stable as a standalone id.
See more info here and here

Using join with Content Provider to query from multiple tables

I am trying to retrieve data from 7 tables and needs to join them to retrieve data , and I am trying to implement Content Provider for this project. I got lots of sample to follow for a single table execution using content provider, but can't get a strong sample to follow for the join case. And in advance I need to pass a string parameter to query for joining table on the base of that string parameter.
for sample I was trying with two tables with not much of luck to pass parameter
case JOIN_FOR_ALL_EVENT_TABLE:
//it is used to get Id but I need a string parameter to pass
//it here, and I don't know how can I achieve that.
//This works for only case of id not the String.
// And the event_id in my case is a string needs to have - symbol
//on it.
_id = ContentUris.parseId(uri);
Log.e("************* args ",_id + "");
retCursor = db.rawQuery(
"SELECT * " +
"from " +
"event_info" + " LEFT JOIN "
+ "wild_animal_info" + " ON "
+ "event_info.event_id"
+ " = " + "wild_animal_info.event_id"+" WHERE "+"event_info.event_id"+" ="+ args[0] ,null);
break;
default:
And the dbHelper looks like this for join
/**
* Method to get all data using join
*/
public void getAllDataFromEventId(String eventId){
Cursor cursor = myCR.query(ContentUris.withAppendedId(
TigerContract.CONTENT_URI_RELATIONSHIP_JOIN, id),null,null,null,null);
if(cursor.moveToFirst()){
Log.v("***********","size of crsor "+cursor.getCount());
cursor.moveToFirst();
Log.v("********NEW ",cursor.getString(1));
Log.v("********NEW ",cursor.getString(2));
Log.v("********NEW ",cursor.getString(4));
Log.v("********NEW ",cursor.getString(5));
}else{
cursor.close();
Log.v("*********New ","Didn't got anything there");
}
And all other thing I did in contract class for Join are
public static final String PATH_JOIN_FOR_ALL_EVENT_TALBE = "join_for_all_event_table";
public static final Uri CONTENT_URI_RELATIONSHIP_JOIN= BASE_CONTENT_URI.buildUpon().appendPath(PATH_JOIN_FOR_ALL_EVENT_TALBE).build();
public static final String CONTENT_TYPE_JOIN = "vnd.android.cursor.item/" + CONTENT_AUTHORITY +"/"+ PATH_JOIN_FOR_ALL_EVENT_TALBE;
So, I am waiting for a strong sample to follow on for join case in Content Provider. And thanks in advance for all the good people out there.

List all Google contact groups in Android

I would like to list all contact groups in a Google account, and make it possible for a user to select some of the groups as "special". When an incoming number belongs to one of the "special" groups, I want to take actions.
I can't find too many examples around this. Has someone done anything similar that you would like to share?
You have three questions here:
How to enumerate the contact groups for a given account.
How to mark a group as special.
How to take action on contacts in that group.
So, going down the list...
1. Enumerating contact groups
The ContactsContract.Groups table stores the list of contact groups on the system. So, you'll want to issue a query that looks like this:
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = ContactsContract.Groups.CONTENT_URI;
Log.i(TAG, "URI: " + uri);
String[] projection = new String[] {
ContactsContract.Groups._ID,
ContactsContract.Groups.TITLE
};
return new CursorLoader(this, uri, projection, null, null, null);
}
This loader will get you the list of all the groups on the system, and their database IDs.
How to mark a group as special
This is something your application will need to take care of. Just maintain a list of group IDs that are in your special list.
To determine whether a contact is in the "special" group, you can query the ContactsContract.Data table using a SQL where clause like the following:
String where = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
+ "="
+ groupid
+ " AND "
+ ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE
+ "='"
+ ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE
+ "'";
where groupid is the database ID of the group you've marked as special. (If you have more than one group, start adding OR clauses.)
(You need to check for the CONTENT_ITEM_TYPE mimetype because the ContactsContract.Data table is used for storing arbitrary metadata for contacts, and the meaning of the columns in that table vary by mimetype. GroupMembership.GROUP_ROW_ID is simply a pointer to the column data1.)
3. How to take action on contacts in that group
This depends on what you're trying to accomplish, but in general, you'll create a broadcast receiver that listens for android.intent.action.PHONE_STATE. If the state in TelephonyManager.EXTRA_STATE is listed as TelephonyManager.EXTRA_STATE_RINGING, then you can get the phone number by looking in TelephonyManager.EXTRA_INCOMING_NUMBER to find the phone number.
(There's sample code for this attached to this article: http://www.krvarma.com/2010/08/detecting-incoming-and-outgoing-calls-in-android/)
You'll then need to check the Contacts database again to find any known contacts with that phone number. You can do this using the ContactsContract.PhoneLookup table.
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
resolver.query(uri, new String[]{PhoneLookup.DISPLAY_NAME,...
That will get you the Contact's database ID in the ContactsContract.Contacts table. The last step here is to match that up with the ContactsContract.RawContacts rows for that contact (search using the CONTACT_ID column), and find the set of RawContacts _IDs that represent that contact. You'll then use this to search through the group membership table, as described above.
I found way to get correct groups by all accounts in Android:
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
Uri uri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
String[] projection = null;
//this is correct selection for retrieving groups from android
String selection = ContactsContract.Groups.ACCOUNT_TYPE + " NOT NULL AND " +
ContactsContract.Groups.ACCOUNT_NAME + " NOT NULL AND " + ContactsContract.Groups.DELETED + "=0";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
selection += " AND " + ContactsContract.Groups.AUTO_ADD + "=0 AND " + ContactsContract.Groups.FAVORITES + "=0";
}
String[] selectionArgs = null;
String sortOrder = ContactsContract.Groups.TITLE + " ASC";
Loader<Cursor> loader = null;
switch(loaderId) {
case GROUPS_LOADER:
loader = new CursorLoader(getActivity(), uri, projection, selection, selectionArgs, sortOrder);
break;
default:
break;
}
return loader;
}
source: grepcode.com

SQLite Database Problem while delete

Hi to All I am new to Android.
I am using SQLite DataBase in my Application
meanwhile I am Written Queries using +
Like delete from tablename where value = + value;
this is my query
String delete_query = "delete from " + tableName
+ " where title = '" + title + "'";
database.execSQL(delete_query);
I want to write this Query using placeholder ?.
so that i tried
database.delete(tableName, title + "?" , new String[] {title});
instead "?" i tried (?)/('?')/'?'
but it is giving me an error....
can any one tell me how to write appropriate query using ?.....
Thanks in Advance.
Mahaveer
Make sure you have put the equal sign:-
database.delete(tableName, title + "=?" , new String[] {title});
As far as possible, try to use the less raw queries you can. Two advantages:
Query parameters will be escaped by the system (protection against SQL injection)
The code will be more readable
See the delete function of SQLiteDatabase class
public int delete (String table, String whereClause, String[]
whereArgs)
Convenience method for deleting rows in the
database.
table the table to delete from
whereClause the optional WHERE clause
to apply when deleting. Passing null will delete all rows.
Returns the number of rows affected if a whereClause is passed in, 0
otherwise. To remove all rows and get a count pass "1" as the
whereClause.
In your case:
final String where = "title=?";
final String[] args = new String[] { title };
database.delete(tableName, where, args);

Categories

Resources