I'm using the CursorJoiner to Join the Media.Image and Thumbnail table in order to select Thumbnails from a specific Album without resorting to actual SQL.
However, there seems to be a mismatch between the data in the Media.Image table and in the corresponding Thumbnails table. There are always less Thumbnails then there are actual images (but I know the thumbnails exists on disk).
I'm using the following code:
public ThumbnailSelector(Context context) {
mContext = context;
}
public List<LocalThumbnail<Integer>> getSqlThumbnailsFromBucket(String bucket) {
return null;
}
protected Cursor getImageMediaCursor(String bucket) {
String[] projection = { Media._ID, Media.BUCKET_DISPLAY_NAME };
String selection = String.format("(%s='%s')", Media.BUCKET_DISPLAY_NAME, bucket);
Cursor cursor = mContext.getContentResolver().query(Media.EXTERNAL_CONTENT_URI, projection,
selection, null, Media._ID + " ASC");
cursor.moveToFirst();
return cursor;
}
protected Cursor getImageThumbnailCursor() {
String[] projection = { Thumbnails.IMAGE_ID, Thumbnails.DATA };
String selection = String.format("%s=%d", Thumbnails.KIND, Thumbnails.MINI_KIND);
Cursor cursor = mContext.getContentResolver().query(Thumbnails.EXTERNAL_CONTENT_URI,
projection, selection, null, Thumbnails.IMAGE_ID + " ASC");
cursor.moveToFirst();
return cursor;
}
public List<LocalThumbnail<Integer>> getThumbnailsFromBucket(String bucket) {
List<LocalThumbnail<Integer>> thumbnails = new ArrayList<LocalThumbnail<Integer>>();
Cursor imageThumbnailCursor = getImageThumbnailCursor();
Cursor imageMediaCursor = getImageMediaCursor(bucket);
CursorJoiner joiner = new CursorJoiner(imageMediaCursor, new String[] { Media._ID },
imageThumbnailCursor, new String[] { Thumbnails.IMAGE_ID });
int idColumnIndex = imageThumbnailCursor.getColumnIndex(Thumbnails.IMAGE_ID);
int dataColumnIndex = imageThumbnailCursor.getColumnIndex(Thumbnails.DATA);
for (CursorJoiner.Result joinerResult : joiner) {
switch (joinerResult) {
case RIGHT: {
break;
}
case LEFT: {
break;
}
case BOTH:
thumbnails.add(new LocalThumbnail<Integer>(imageThumbnailCursor
.getInt(idColumnIndex), "file://"
+ imageThumbnailCursor.getString(dataColumnIndex)));
break;
}
}
imageThumbnailCursor.close();
imageMediaCursor.close();
return thumbnails;
}
Anyone know why this mismatch happens?
Related
I'm working on a custom image gallery for my application but I got a serious issue under Samsung Galaxy S4 and S5. The following code works great on HTC and Xperia devices but not at all on Samsung ones.
Basically, the pathes are always null on these devices.
Here is the code I made.
In big lines, it returns a list of categories containing a name and a list of images. The aim is to provide a folder based gallery like the native one.
Should you have any idea why it fails on Samsung galaxy ?
Thanks for your help.
public List<Category> getCategories()
{
Map<String, Category> map = new HashMap<String, Category>();
String[] projection = new String[] {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
};
ContentResolver cr = getContentResolver();
Cursor cur = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_TAKEN + " DESC");
if (cursor == null) return;
if (cur.moveToFirst())
{
Category category = null;
long id = 0L;
String bucket = null;;
int idColumn = cur.getColumnIndex(MediaStore.Images.Media._ID);
int bucketColumn = cur.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
do
{
id = cur.getLong(idColumn);
bucket = cur.getString(bucketColumn);
if (map.get(bucket) == null)
{
category = new Category(bucket);
map.put(bucket, category);
}
category = map.get(bucket);
category.addImage( idToImage(id) );
}
while (cur.moveToNext());
}
return map.values().toArray();
}
private Image idToImage(long id)
{
Image image = new Image();
image.setThumbnail( getThumbnail(id) );
image.setImage( getImage(id) );
return image;
}
private String getThumbnail(long id)
{
String path = null;
Cursor cursor = MediaStore.Images.Thumbnails.queryMiniThumbnail(getContentResolver(), id, MediaStore.Images.Thumbnails.MINI_KIND, null);
if( cursor != null && cursor.getCount() > 0 )
{
cursor.moveToFirst();
path = cursor.getString( cursor.getColumnIndex( MediaStore.Images.Thumbnails.DATA ) );
cursor.close();
}
return path;
}
private String getImage(long id)
{
String path = null;
String[] projection = {MediaStore.Images.Media.DATA};
String where = MediaStore.Images.Media._ID + " = " + id;
Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, where, null, null);
int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
if( cursor != null && cursor.getCount() > 0 )
{
cursor.moveToFirst();
path = cursor.getString(dataColumn);
}
return path;
}
It will work
/**
* Get actual file path from gallery
*/
public String getPath(Uri uri, Context ctx) {
String[] projection = {MediaStore.Images.Media.DATA};
String result;
Cursor cursor = ctx.getContentResolver().query(uri, projection, null, null, null);
if (cursor == null) {
return null;
}
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
result = cursor.getString(column_index);
cursor.close();
return result;
}
I'm trying to pull a list of contacts and attaching all of the contacts associated with a given person. The problem I am having is that my query is returning back all contacts that have ever been emailed. I don't want to include every contact I've emailed however, I just want the main contacts (like what is displayed in the built-in People app).
In the code below I was trying to limit the emails by using a ContactsContract.Contacts.CONTENT_URI query to limit the returned emails, but that doesn't seem to work.
Any help would be greatly appreciated.
private ArrayList<ContactFriend> allContacts;
private Cursor contactsCursor;
private Cursor emailCursor;
void load() {
getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this);
getLoaderManager().initLoader(EMAILS_LOADER_ID, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == CONTACTS_LOADER_ID) {
String[] projection = {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
ContactsContract.Contacts.PHOTO_THUMBNAIL_URI,
};
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC";
return new CursorLoader(getActivity(),
ContactsContract.Contacts.CONTENT_URI,
projection,
null,
null,
sortOrder);
} else if (id == EMAILS_LOADER_ID) {
String[] projection = {
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Email.CONTACT_ID
};
String sortOrder =
ContactsContract.CommonDataKinds.Email.CONTACT_ID + ", " +
ContactsContract.CommonDataKinds.Email.DATA + " ASC";
return new CursorLoader(getActivity(),
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
projection,
null,
null,
sortOrder);
} else {
// should never happen
return null;
}
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
int id = loader.getId();
if (id == CONTACTS_LOADER_ID) {
contactsCursor = cursor;
} else if (id == EMAILS_LOADER_ID) {
emailCursor = cursor;
}
if (contactsCursor != null && emailCursor != null) {
init();
}
}
void init() {
if (allContacts == null) {
allContacts = getAllContacts();
}
// do stuff with allContacts
}
private ArrayList<ContactFriend> getAllContacts() {
HashMap<Integer, ArrayList<String>> emails = getEmailMap();
ArrayList<ContactFriend> result = new ArrayList<ContactFriend>();
contactsCursor.moveToFirst();
do {
String name = contactsCursor.getString(contactsCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
String photo = contactsCursor.getString(contactsCursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI));
int id = contactsCursor.getInt(contactsCursor.getColumnIndex(ContactsContract.Contacts._ID));
ArrayList<String> contactEmails = emails.get(id);
if (!contactEmails.isEmpty()) {
result.add(new ContactFriend(name, contactEmails, photo, id));
}
} while (contactsCursor.moveToNext());
return result;
}
private HashMap<Integer, ArrayList<String>> getEmailMap() {
HashMap<Integer, ArrayList<String>> result = new HashMap<Integer, ArrayList<String>>();
while (contactsCursor.moveToNext()) {
int id = contactsCursor.getInt(contactsCursor.getColumnIndex(ContactsContract.Contacts._ID));
result.put(id, new ArrayList<String>());
}
Set<Integer> contactIds = result.keySet();
while (emailCursor.moveToNext()) {
int id = emailCursor.getInt(emailCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.CONTACT_ID));
if (contactIds.contains(id)) {
String email = emailCursor.getString(emailCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
result.get(id).add(email);
}
}
return result;
}
Adding this in case someone else has the same problem. Adding ContactsContract.Contacts.IN_VISIBLE_GROUP to the projection and then selecting only those entries where that value is equal to 1 gives me what I'm looking for.
I am trying to get email ids of uses contacts. For that I am using Cursor Loader. There is one problem I am getting duplicate email ids also. How to remove email duplicacy. Should I use raw query "SELECT DISTINCT" instead of using CursorLoader or there is some other solution?
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA};
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP +"='1' AND " + Email.DATA +" IS NOT NULL AND " + Email.DATA +" != \"\" " ;
//showing only visible contacts
String[] selectionArgs = null;
return new CursorLoader(this, ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
}
I recently ran into this problem. It appears that the CursorLoader does not have an implementation of "DISTINCT". My workaround adds a few lines to the onLoadFinish method and extends the BaseAdapter to accept a List parameter:
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String projection[] = {
CommonDataKinds.Phone._ID,
CommonDataKinds.Phone.DISPLAY_NAME,
};
String select = "((" + CommonDataKinds.Phone.DISPLAY_NAME + " NOTNULL) and " + CommonDataKinds.Phone.HAS_PHONE_NUMBER + " > 0)";
String sort = CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
CursorLoader loader = new CursorLoader(
mContext,
CommonDataKinds.Phone.CONTENT_URI,
projection,
select,
null,
sort
);
return loader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
List<String> displayNames = new ArrayList<String>();
cursor.moveToFirst();
while(!cursor.isAfterLast()){
String name = cursor.getString(cursor.getColumnIndex(CommonDataKinds.Phone.DISPLAY_NAME));
if(!displayNames.contains(name))
displayNames.add(name);
cursor.moveToNext();
}
mAdapter.swapCursor(displayNames);
}
Here is my BaseAdapter class:
public class AdapterAddContacts extends BaseAdapter{
private List<String> mData = new ArrayList<String>();
private Context mContext;
public AdapterAddContacts(Context context,List<String> displayNames){
mData = displayNames;
mContext = context;
}
#Override
public int getCount() {
if(mData != null)
return mData.size();
else
return 0;
}
#Override
public Object getItem(int pos) {
return mData.get(pos);
}
#Override
public long getItemId(int id) {
return id;
}
#Override
public View getView(int pos, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.entry_add_contacts,parent,false);
String data = mData.get(pos);
TextView textName = (TextView)view.findViewById(R.id.my_contacts_add_display_name);
textName.setText(data);
textName.setTag(data);
return view;
}
public void swapCursor(List<String> displayNames){
mData = displayNames;
this.notifyDataSetChanged();
}
You should be able to modify this specifically for your needs.
Inspired by #mars, I have a solution that does not need a modification of the adapter. The idea is to delete the duplicates of the cursor; as there is no way to do it, we create a new cursor whithout the duplicates.
All the code is in onLoadFinished:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
MatrixCursor newCursor = new MatrixCursor(PROJECTION); // Same projection used in loader
if (cursor.moveToFirst()) {
String lastName = "";
do {
if (cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)).compareToIgnoreCase(lastName) != 0) {
newCursor.addRow(new Object[]{cursor.getString(0), cursor.getString(1), cursor.getString(2) ...}); // match the original cursor fields
lastName =cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
}
} while (cursor.moveToNext());
}
mContactsAdapter.swapCursor(newCursor);
}
I used a small hack in my project - an SQL injection, like that:
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(
this,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] {
"DISTINCT "+ MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME},
null, null, null);
}
This code returns only bundle names and their IDs from Gallery.
So, I'd rewrite your code like that:
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
String[] projection = new String[] {
"DISTINCT " + ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Email.DATA};
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP +"='1' AND " + Email.DATA +" IS NOT NULL AND " + Email.DATA +" != \"\" " ;
//showing only visible contacts
String[] selectionArgs = null;
return new CursorLoader(this, ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
}
You can put setDistinct in your content provider.
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setDistinct(true);
If you are worried about performance and don't want to play around with cursor again in onLoadFinished(), then there is a small hack
I combined following two solutions from SO.
select distinct value in android sqlite
CursorLoader with rawQuery
And here is my working solution:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String tableName;
/*
* Choose the table to query and a sort order based on the code returned
* for the incoming URI.
*/
switch (uriMatcher.match(uri)) {
case NOTIFICATION:
tableName = NOTIFICATIONS_TABLE_NAME;
break;
case NOTIFICATION_TIMESTAMP:
Cursor cursor = db.query(true, NOTIFICATIONS_TABLE_NAME, projection, selection, selectionArgs, TIMESTAMP, null, sortOrder, null);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
case DOWNLOAD:
tableName = DOWNLOADS_TABLE;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (selection != null) {
selection = selection + "=?";
}
Cursor cursor = db.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
// Tell the cursor what uri to watch, so it knows when its source data
// changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
If you see in this case Table name is same is first 2 cases but i created a dummy Uri to achieve this. May not be a very good approach but works perfectly.
I found a solution
Use DISTINCT keyword in selection Array.
String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, "DISTINCT" + ContactsContract.CommonDataKinds.Email.DATA};
I managed to write add program that upon the user click a button, he will be able to retrieve a phone number that will fill an editText box. The problem is that if a contact has multiple numbers, it will always take the top most number.
I have been reading another thread, Getting Number from Contacts Picker, there is an answer, but I don't get it. As I am new to android programming,
I'll appreciate if anyone out there could give me step by step instructions.
First, you may need to query all contact in phone book.
// Run query
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME
};
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '1'";
String[] selectionArgs = null;
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
// Build adapter with contact entries
Cursor mCursor = managedQuery(uri, projection, selection, selectionArgs, sortOrder);
//
// Bind mCursor to to your Listview
//
After that, when user select a contact in your list view, you make a second query to get label and number of that contact.
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
mCursor.moveToPosition(position);
startManagingCursor(mCursor);
String contactID = mCursor.getString(mCursor.getColumnIndex(ContactsContract.Contacts._ID));
Cursor phoneNumCursor = getContentResolver().query(Phone.CONTENT_URI,
null, Phone.CONTACT_ID + "=?", new String[] { contactID }, null);
phoneNumCursor.moveToFirst();
Vector<String> phoneTypeList = new Vector<String>();
Vector<String> phoneNumberList = new Vector<String>();
while (!phoneNumCursor.isAfterLast()){
int type = phoneNumCursor.getInt(phoneNumCursor.getColumnIndex(Phone.TYPE));
phoneTypeList.add(String.valueOf(Phone.getTypeLabel(getResources(),type,"")));
phoneNumberList.add(phoneNumCursor.getString(phoneNumCursor.getColumnIndex(Phone.NUMBER)));
phoneNumCursor.moveToNext();
}
//
// Feel free to show label and phone number of that contact. ^^
//
Updated:
Below is an example if you want to use Contact Picker.
private static final int CONTACT_PICKER_RESULT = 1001;
protected void startContactPicker(){
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK,Contacts.CONTENT_URI);
startActivityForResult(contactPickerIntent, CONTACT_PICKER_RESULT);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch (requestCode) {
case CONTACT_PICKER_RESULT:
Cursor cursor = null;
String phoneNumber = "";
try {
Uri result = data.getData();
String id = result.getLastPathSegment();
cursor = getContentResolver().query(Phone.CONTENT_URI,
null, Phone.CONTACT_ID + "=?", new String[] { id }, null);
int phoneIdx = cursor.getColumnIndex(Phone.DATA);
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()){
phoneNumber = cursor.getString(phoneIdx);
//
// this will go through all phoneNumber of selected contact.
//
cursor.moveToNext();
}
}
} catch (Exception e) {
} finally {
if (cursor != null) {
cursor.close();
}
numberView.setText(phoneNumber);
}
break;
}
}
}
I've spent a lot of hours searching and reading similar posts, but none of them seem to truly reflect my problem and thus I haven't found anything that works for me.
I've got a database, on which I perform a query, the results of which are stored in a cursor. There's two things to that:
-the query is performed everytime a certain button is pressed (thus the query is inside the OnClickListener for that Button)
-the query returns two different columns with String values, which must be treated separately (one column stores the names which must be shown in the ListView, the other stores the paths to the image associated toa row)
My problem is, I try to create a String[] which I need to pass to the ArrayAdapter creator for the ListView, but trying to assign it a size of Cursor getCount() crashes my activity. I hope the code will be more of an explanation:
OnClickListener searchListener = new OnClickListener() {
public void onClick(View v) {
CardDatabaseOpenHelper helper = new
CardDatabaseOpenHelper(DeckEditorScreen1.this);
SQLiteDatabase db = helper.getReadableDatabase();
String columns[] = {"name","number","path"};
Cursor c = db.query("cards", columns, null, null, null, null, "number");
int count = c.getCount();
String[] resultNameStrings;
if (count != 0) resultNameStrings = new String[count];
else {resultNameStrings = new String[1]; resultNameStrings[1] = "No results";}
// This is the offending code
//Note that if I assign fixed values to resutNameStrings, the code works just
//fine
for (int i = 0; i < count; ++i) {
c.moveToNext();
int col = c.getColumnIndex("name");
String s = c.getString(col);
//Ideally here I would to something like:
//resultNameStrings[i] = s;
col = c.getColumnIndex("number");
int conv = c.getInt(col);
col = c.getColumnIndex("path");
String s2 = c.getString(col);
}
db.close();
ArrayAdapter<?> searchResultItemAdapter = new ArrayAdapter<String>
(DBScreen.this,
R.layout.search_result_item,
resultNameStrings);
ListView searchResultList = (ListView)
DBScreen.this.findViewById(R.id.search_result_list);
searchResultList.setAdapter(searchResultItemAdapter);
}
};
Button search_button = (Button) findViewById(R.id.search_button);
search_button.setOnClickListener(searchListener);
EDITED twice :)
do it in "Android Way" ...
first use CursorAdapter (fx.: SimpleCursorAdapter with overrided
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
Cursor cursor = managedQuery(MobileTraderProvider.CONTENT_URI,
null, null, new String[] { constraint.toString() }, null);
return cursor;
}
then
customAdapter.getFilter().filter(filterText)
// it will call runQueryOnBackgroundThread
second use ContentProvider(it will manage curosors for you ... it will even requery if data changed)
EDIT:
first really use my advice
second before
for (int i = 0; i < count; ++i) {
c.moveToNext();
//...
add c.moveToFirst();
thrid: use
if(c.moveToNext())
{
int col = c.getColumnIndex("name");
//..... rest goes here
}
SECOND EDIT:
MyProvider.java
public class MyProvider extends ContentProvider {
static final String LTAG = "MyAppName";
public static final Uri CONTENT_URI = Uri.parse("content://my.app.Content");
static final int CARDS = 1;
static final int CARD = 2;
public static final String CARDS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/Cards";
public static final String CARD_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/Cards";
static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static final HashMap<String, String> map = new HashMap<String, String>();
static {
//static "Constructor"
matcher.addURI(Constants.AUTHORITY, "Cards", LISTS);
matcher.addURI(Constants.AUTHORITY, "Cards/*", LIST);
map.put(BaseColumns._ID, "ROWID AS _id");
map.put(Tables.Cards.C_NAME, Tables.Cards.C_NAME);
map.put(Tables.Cards.C_NUMBER, Tables.Cards.C_NUMBER);
map.put(Tables.Cards.C_PATH, Tables.Cards.C_PATH);
}
private CardDatabaseOpenHelper mDB;
#Override
public boolean onCreate() {
try {
mDB = new CardDatabaseOpenHelper(getContext());
} catch (Exception e) {
Log.e(LTAG, e.getLocalizedMessage());
}
return true;
}
public int delete(Uri uri, String selection, String[] selectionArgs) {
String table = null;
switch (matcher.match(uri)) {
case CARD:
//overriding selection and selectionArgs
selection = "ROWID=?";
selectionArgs = new String[] { uri.getPathSegments().get(1) };
table = uri.getPathSegments().get(0);
break;
case CARDS:
//this version will delete all rows if you dont provide selection and selectionargs
table = uri.getPathSegments().get(0);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
int ret = mDB.getWritableDatabase().delete(table, selection, selectionArgs);
getContext().getContentResolver().notifyChange(Uri.withAppendedPath(CONTENT_URI, table), null);
return ret;
}
#Override
public String getType(Uri uri) {
switch (matcher.match(uri)) {
case CARDS:
return CARDS_MIME_TYPE;
case CARD:
return CARD_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String table, rowid;
switch (matcher.match(uri)) {
case CARD:
//overriding selection and selectionArgs
selection = "ROWID=?";
selectionArgs = new String[] { uri.getPathSegments().get(1) };
table = uri.getPathSegments().get(0);
break;
case CARDS:
//this version will update all rows if you dont provide selection and selectionargs
table = uri.getPathSegments().get(0);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
int ret = mDB.getWritableDatabase().update(table, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(Uri.withAppendedPath(CONTENT_URI, table), null);
return ret;
}
public Uri insert(Uri uri, ContentValues values) {
String table = null;
switch (matcher.match(uri)) {
case CARDS:
table = uri.getPathSegments().get(0);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
mDB.getWritableDatabase().insert(table, null, values);
getContext().getContentResolver().notifyChange(Uri.withAppendedPath(CONTENT_URI, table), null);
return null;
}
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
switch (matcher.match(uri)) {
case CARDS:
builder.setTables(uri.getPathSegments().get(0));
break;
case CARD:
builder.setTables(uri.getPathSegments().get(0));
selection = "ROWID=?";
selectionArgs = new String[] { uri.getPathSegments().get(1) };
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
builder.setProjectionMap(map);
Cursor cursor = builder.query(mDB.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
if (cursor == null) {
return null;
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
}
CardCursorAdapter.java
class CardCursorAdapter extends SimpleCursorAdapter {
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
}
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
//search in cards.name
String selection = Tables.Cards.C_NAME + " LIKE ?";
String[] selectionArgs = new String[] {"%" + constraint.toString() + "%"};
Cursor cursor = managedQuery(Uri.withAppendedPath(MyProvider.CONTENT_URI, Tables.Cards.Name),
getCursor().getColumnNames(), selection, selectionArgs, null);
return cursor;
}
}
Tables.java
public static class Tables {
//table definition
public static interface Cards {
public static final String NAME = "cards";
public static final String C_NAME = "name";
public static final String C_NUMBER = "number";
public static final String C_PATH = "path";
}
//other tables go here
}
AndroidManifest.xml
</manifest>
</application>
<!-- ....... other stuff ....... -->
<provider android:name="MyProvider" android:authorities="my.app.Content" />
</application>
</manifest>
then in activity
onCreate(...){
listView.setAdapter(new CardCursorAdapter(this, R.layout.listrow,
managedQuery(Uri.withAppendedPath(MyProvider.CONTENT_URI, Tables.Cards.NAME),
new String[] { BaseColumns._ID, Tables.Cards.C_NAME, Tables.Cards.C_NUMBER, Tables.Cards.C_PATH },
null,null, number),
new String[] { Tables.Cards.C_NAME, Tables.Cards.C_NUMBER, Tables.Cards.C_PATH },
new int[] { R.id.tName, R.id.tNumber, R.id.tPath }));
}
OnClickListener searchListener = new OnClickListener() {
public void onClick(View v) {
DeckEditorScreen1.this.listView.getAdapter().getFilter().filter("text for search in name column of card table set me to empty for all rows");
}
}
Ok, I've done some testing and I think I know what was the problem. Java allows constructs like:
String[] whatever;
if (something) whatever = new String[avalue];
else whatever = new String[anothervalue];
The crash occurs if you don't assign a concrete value to each and every field whatever[i]. The rest of the code is now just fine, though I've added Selvin's correction
if (c.moveToNext) ...
c.moveToFirst() is not correctly used in my case, as the for iterates count times. If you perform a moveToFirst first, you're always missing the first element pointed by the cursor.