Optimising AutoCompleteTextView for Android Contacts - android

I have implemented an autocomplete feature to allow the user to start typing a contact and for that contact to appear in a dropdown list using an autoCompleteTextView, like the same way it works when picking contacts for messages or e-mails.
As I don’t want a variable holding all of the contacts at once since this could be very big, I populated my ArrayList as the user enters letters into the contact field.
I am setting it up like this:-
peopleList = new ArrayList>();
adapter = new SimpleAdapter(this, peopleList, R.layout.customcontcell ,new String[] { "Name", "Phone" , "Type" }, new int[] { R.id.ccontName, R.id.ccontNo, R.id.ccontType });
txtPhoneNo.setAdapter(adapter);
Then when the user starts typing a name it grabs all the rows from the Contacts that match that, this is done in my function “QueryContacts” like so:-
selectionWhere = ContactsContract.Contacts.DISPLAY_NAME+" LIKE '" + name + "%'";
//Cursor to retrive contact details.
Cursor people = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, selectionWhere, null, null);
This cursor is then used to populate my peopleList which is being used by the Adapter.
In this state it works, however without some checks the above code ends up retrieving ALL of the contacts initially (as no name is entered so it filters nothing) and it runs every time a new letter is typed. This is jittery and slow.
So I wanted to put some simple checks to limited it to only retrieving the contacts when 2 letters have been entered, and not retrieving anymore unless it goes below 2 letters then back to 2 again.
So around my QueryContacts functions I add:-
if(name.length() < 2)
mGotContacts = false;
//If the length is two letters long and we haven't queried already, query for the name.
if(name.length() == 2 && mGotContacts == false)
{
// Cursor code
// Populate list with cursor data.
}
Problem is now the autocompleteTextView no longer drops down, I have checked and the variable peopleLists which populated my SimpleAdapter is being correctly updated.
So, am I doing this a daft way? Should I just grab all of the data in one go and let the AutoCompleteTextView filter it?
Is their a better way of doing this and why does it no longer work with those checks in my QueryContacts function?

Have a look at the threshold property of AutoCompleteTextView. The threshold defines the number of characters that must be entered before the autocomplete drop down is shown. I am not sure what effect this would have on performance, but since its native to android I imagine its as fast as reasonably possible.
setThreshold method documentation

Related

Why is ContentResolver.query() returning duplicate entries from contacts?

So I'm reading in these contacts using Android's native implementation, using a test phone with about 1,056 contacts on it. The problem I'm having is that it's rather slow, so I'm logging the outcomes of my cursor.moveToNext(), and seeing that it's reading in contacts past the last in an overlapping, duplicating fashion. It does, however, have previous logic implemented so the app compares the hashed version of this list of entries to the previously saved one every second... which eventually brings the entries back to the correct value, order, and contents. However, using the code below, at worst it's pulling the entire contact list AGAIN, and then putting it in there with the same list (essentially reads the same address book twice and reads it in, doubling entries). This gets worse the larger your contact list is (smaller books, like this Galaxy I have with 9 contacts, don't seem to be affected; whereas my phone with about 106 is slightly, and this tester with 1,053 significantly) with the larger phones I've tested taking upwards of a minute and a half to two minutes before it's fully updated, accurate, and done.
What's confusing is that it somehow, even after all this duplication, the check manages to come back and be exactly what it was supposed to be (i.e 1,053 contact phone I see it adding the 2,106th row, then it immediately loads the proper 1,053 contact book).
Here's how my cursor is defined:
Cursor c = mContext.getContentResolver().query(addressBookURI,
CONTACTS_QUERY_PROJECTION, CONTACTS_QUERY_SELECTION, null, CONTACTS_QUERY_SORT);
Here's each of the components of said Cursor "c":
private static final Uri addressBookURI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "1").build();
private static final String[] CONTACTS_QUERY_PROJECTION = {
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.HAS_PHONE_NUMBER,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Data.DATA1,
ContactsContract.Data.DATA2,
"account_name"
};
private static final String CONTACTS_QUERY_SELECTION = "in_default_directory=1 and has_phone_number=1 and mimetype='vnd.android.cursor.item/phone_v2'";
private static final String CONTACTS_QUERY_SORT = "contact_id ASC";
Processing the Cursor "c"'s reads:
List<String[]> entries = new ArrayList<>();
while (c.moveToNext()) {
String id = c.getString(0);
String name = c.getString(2);
String phone = c.getString(3);
String type = c.getString(4);
String account_name = c.getString(5);
Log.d(sTag, "contact:row:" + id + ":" + name + ":" + phone + ":" + type + ":" +
account_name);
entries.add(new String[]{id, name, phone, type});
It's in that Log.d() statement where I can see duplicates of contacts, wherein the only difference between the original contact and follow-up duplicates is the "id" variable (keeps increasing, period).
For example (if it happened with smaller amounts of contacts; all values are made up):
contact:row:1:Maurine Lastpass:4145737719:3:D8:0B:9A:CC:88:BF
contact:row:2:Blondell Sosig:4013008122:3:D8:0B:9A:CC:88:BF
contact:row:3:Amber Altingle:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:4:Frank Helgenson:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:5:Hiro Xin:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:6:Baley Balix:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:7:Henry Halgor:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:8:Hammy Xevronic:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:9:Maurine Lastpass:4145737719:3:D8:0B:9A:CC:88:BF
contact:row:10:Blondell Sosig:4013008122:3:D8:0B:9A:CC:88:BF
contact:row:11:Amber Altingle:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:12:Frank Helgenson:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:13:Hiro Xin:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:14:Baley Balix:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:15:Henry Halgor:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:16:Hammy Xevronic:6316773675:2:D8:0B:9A:CC:88:BF
I've tried storing the very first contact and letting it read from the Cursor until it matches up with that contact, but since the "id" variable keeps increasing, it makes the contact different enough to be ignored. I removed the ID portion of the contact, changed the URI from the default to one with an explicit REMOVE_DUPLICATE_ENTRIES = 1 parameter on it, and I tried to bring in the "real" count using ContactsContract.Data._COUNT in order to check the number of entries on the cursor against it, but that just crashes with "no such column _COUNT".
Is there any reason why the cursor would be pulling duplicate contacts like this? Is there something wrong with the structure of my query that is causing this sort of duplication?
let's recap the way ContactsContract DB is organized:
Contacts table - contains one row per contact, but hardly any information
RawContacts table - can have multiple rows, each assigned to a single contact-ID (from the previous table), contains a logical group of multiple Data rows usually for a single SyncProvider such as Google.
Data table - contains the actual data of a RawContact, each row has a MIMETYPE column which states what kind of data this row is about (phone, email, name, etc.) + 15 data columns to hold the information itself.
There are also pseudo tables, such as ContactsContract.CommonDataKinds.Phone which you are querying in your code, which basically queries over the Data table but with a specific MIMETYPE value, for example Phone.CONTENT_ITEM_TYPE.
In your code you are querying over CommonDataKinds.Phone.CONTENT_URI which means you are asking for all the different phone numbers in the DB.
So if a single contact has 3 phone numbers, you will get 3 rows for that contact (each with a different phone number).
However looking at your output, it seems like every contact has a single phone number in the DB, but it looks like the contacts themselves are duplicated.
So for example Amber Altingle has contact ID 3 and also 11, which means you have two separate contacts named Amber Altingle.
This is not duplication in the query code, but possibly duplication in the contact creation code.

SQLite Change Sort Order for items on database

So imagine this is a quick mockup of my database:
The items from the database are presented to the user per list, each list being displayed on a new fragment, which are displayed on a viewpager. So let's say in this hypotetical case, there would be two fragments on the viewpager, first fragment would display first_list and second fragment would display second_list. Here's the code for that query:
public static Cursor getListItems (final Context context, String listName) {
if (mDatabase == null || !mDatabase.isOpen())
open(context); //This gets the writable db.
String where = LIST_NAME + " = '" + listName + "'";
return mDatabase.query(TABLE_LIST_ITEMS, PROJECTION_LIST_ITEMS,
where, null, null, null, SORT_ORDER);
}
Where SORT_ORDER is order_in_list, this works well, to begin with.
Now, the listviews are re-arrangeable using a public library, which attempts to allow the user to control the order of the items in each list. Here's where I am having issues, there is no add(int index, Object object) for the cursor, or some other easy way to manage the sorting. I first thought I could simply call mDatabase.update() to change the value for order_in_list but that works, but the results are not as intended. For example, user drags item two to position zero, remeber: zero-index values, we would now have two items with order_in_list as zero. And although I can call mDatabase.update() on item one to update his position to one, imagine how much work that'd be to handle several items on a well-formed database.
Does anyone have any good suggestions on how I could work this out? I thought I had been clever by adding the extra col for sorting purposes :(
INB4:
Yes, I Know arrays handle this well. But the database doesn't only store 4 cols, it has many more fields. Populating arrays each time from the database would be a waste of time and effort. And I would, anyways, have to write back to the database when the app is closed.
EDIT So I changed the listview to only display one String of text, and further columns upon actual clicking on the item (and therefore displaying a new fragment with the specified list item data). This allowed me to simply keep an ArrayAdapter which easily handles the drag and drop. During onStop, I update the reference only if there was a change that required to be saved:
#Override
public void onStop() {
if (updateDbOnExit) {
//Update rows on database.
for (int i = 0; i < items.size(); i++) {
//Set the order in list be the actual order on the array.
Constants.LogMessage("Updating db content");
DbManager.moveListItemTo(getActivity(), items.get(i), i);
}
updateDbOnExit = false;
}
super.onStop();
}
Where MoveListItemTo updates the value for order_in_list:
public static void moveTaskItemTo (final Context context, String item, int to) {
if (mDatabase == null || !mDatabase.isOpen())
open(context);
String where = COL_CONTENT + " = '" + item+ "'";
ContentValues mContentValues = new ContentValues();
mContentValues.put(ORDER_IN_LIST, to);
int rows = mDatabase.update(TABLE_LIST_ITEMS, mContentValues, where, null);
Constants.LogMessage(rows + " row updated. Item moved to position: " + to);
close();
}
That will work for now. However, I am still interested on knowing if there is an alternate way, especially when for example, the adapter is using data from more than one column on the database, and is therefore required to use a CusorAdapter and not a regular ArrayAdapter, which in turn requires the Database itself to update upon each Drag and Drop to reflect the change on the UI via cursorAdapter.swapCursor(). As stated, updating ALL of the items on a database upon each drag (which realistically doesn´t happen that often btw), is expensive, updating only Two rows, would be a saner choice.
I just meant I wanted a more effective way to update the fields in the db, rather than manually updating each and every single row
Make the user-specified-order column a decimal, not an integer. Then you need to update only the moved row(s).
Allow negative numbers.
0.00 cat
1.00 aardvark
2.00 wolf
3.00 dog
If "dog" is dragged above "wolf" then "dog" becomes 1.50 and no need to change other rows. If "aardvark" is dragged above "cat" (special case -- prepending to list rather than inserting between rows) then subtract 1 from the topmost value, and "aardvark" becomes -1.00.
This will require you to know the values of the adjacent rows, but you won't have to update them. Only the moved row's value must change.
I would suggest that you have an additional column, user_specified_order which would represent the user's reordering of the rows in the UI via drag-drop.
You must update each row when its user_specified_order value is invalidated by the drag-drop repositioning. When to persist that value is up to you -- either at the "end" of the user's manipulations, however that be defined (e.g. click on Save button) or after each drag/drop if there is no clearcut UI indicator of "end of manipulation".
EDIT: ContenProvider in Android:
Android - Can you update a Cursor for SQLite results?
Android SQLite transactions:
Android Database Transaction

android sqlite delete row not working

I am having an odd problem deleting rows from a sqlite database. The first time I try deleting a row, it seems to work fine. However, anytime thereafter, I get no errors, it simply does not remove the selected row. If I uninstall the app and re-run it from eclipse (I have my tablet hooked up to my pc for testing) then the first delete works again.
Here is the code that I am using to populate a Gallery View from my database
Cursor cursor = _garmentAdapter.fetchAllLooks();
cursor.moveToLast();
final ArrayList<Bitmap> al = new ArrayList<Bitmap>();
_labels=new ArrayList<String>();
int totalGarments=0;
for(int i=cursor.getCount()-1; i>=0; i--) {
totalGarments++;
Bitmap bd=BitmapFactory.decodeFile(cursor.getString(3));
String label=cursor.getString(1);
al.add(bd);
_labels.add(label);
cursor.moveToPrevious();
}
Toast.makeText(BrowseLooks.this, "ITEMS=="+totalGarments, Toast.LENGTH_SHORT).show();
This toast message is displaying the number of items on screen. For example, if I create 3 items, then delete 1, it shows 2 on screen and the toast says ITEMS==2. However, if I delete another one, I still have 2 items on screen and ITEMS==2. So it seems the cursor is working correctly, but the database is not.
Here is the code for the delete row in the database:
return mDb.delete(LOOKS_TABLE, KEY_ROWID+"="+rowId, null)>0;
I have also logged rowId as being correct when mDb is called.
Once again, I am not getting any errors, the code is just not working. Any help would be very much appreciated.
Try this
db.delete(LOOKS_TABLE, KEY_ROWID+ " = ?",
new String[] { String.valueOf(rowId) });
I have found the answer to my problem. I did not fully understand the autoincrement of the sqlite database. Because of that, I was deleting rows that were already empty. The way the database increments is to create an entry higher than all previous entries not just the current highest entry. Therefore, the indexes of my on-screen items did not correspond to the indexes in my database table.

Cursor Adapter with multi select

I've written an APP the uses has a small SQL lite DB and using a cursor adapter I can retrieve the records and populate a list view with them. from there I can get the Id of a selected item and delete it from the DB which works great. the issue I have is that as the DB grows deleting one row at a time would be slow and frustrating so I wanted to know if there was any way to allow multiple selections possibly with check boxes or by even changing the text color of the items selected so that I can retreiving their relative ID's.
I have read some posts that talk about custom cursor adapters but I am not sure how to adapt them to my code. I have posted my code below.
private void fillData() {
Cursor c = mDbHelper.fetchAllNotes();
startManagingCursor(c);
String[] from = new String[] {DBHelper.KEY_FIELD0,
DBHelper.KEY_FIELD1,
DBHelper.KEY_FIELD2,
DBHelper.KEY_FIELD3,
DBHelper.KEY_FIELD4};
int[] to = new int[] {R.id.text,R.id.text2,R.id.text3,R.id.text4,R.id.text5};
SimpleCursorAdapter dblist = new SimpleCursorAdapter(this, R.layout.row, c, from, to);
setListAdapter(dblist);
}
Thanks.
I have not done that myself but I did find this tutorial that explains how to add check boxes to a list:
http://www.androidpeople.com/android-listview-multiple-choice-example/
You can use that example to allow multipe selection of items in a list control. Instead of using the array array adaptor as in this example instead use your SimplecursorAdapter. The only gotcha you will have to work out is that the example uses android.R.layout.simple_list_item_multiple_choice for the list entries and you need to replace that with your layout since you obviously want a few more than one text field.
I'm not at home so I cannot try it out but I did find this:
http://www.mail-archive.com/android-developers#googlegroups.com/msg21920.html
which seems to refer to making your own multiple_choice layout for a list item.
After the user has made their choices you can remove multiple records at one time by running SQL in your database adaptor:
DELETE FROM test WHERE _id IN (1, 3, 6, 7)
where the list of numbers are the selected id values.

How to go through every record in SQLite db?

I think it's kinda easy one but still I'm new to android programming so please have patience. I want to know how can I get the number of records (rows) in a specific table in my db. I need this so I can create a loop to go through every record and add each one of it to the specific Array and display it later on. This is the source:
db.openDataBase(); // open connection with db
Cursor c = db.getTitle(5); // loop here through db, right now I'm fetching only one record
startManagingCursor(c);
//adding areas to the list here
Area o1 = new Area();
o1.setOrderName(c.getString(1) + c.getString(2));
m_areas.add(o1);
db.close();
Does anyone can help me with this please? Thx in advance!
SELECT COUNT(*) FROM tablename
To get the number of rows in the cursor, use getCount.
To get the amount of total rows in a table, either use reinierposts solution, or do a select which select all rows in the table and get the count from the cursor. I'm guessing his solution is quicker though unless you actually need all the rows in the table.
Such a query would be:
SELECT * FROM footable;
You don't really need to get a count of how many first; instead, create a db.getTitles() function that returns all of the rows and returns a Cursor, then loop over the Cursor. Right now you probably have a query that looks something like SELECT ColumnA, ColumnB FROM Titles WHERE id = 5; just copy the function, remove the parameter and take off the WHERE clause so it looks like just SELECT ColumnA, ColumnB FROM Titles.
Then your code would look something like this:
db.openDataBase(); // open connection with db
Cursor c = db.getTitles();
startManagingCursor(c);
//adding areas to the list here
if (c != null && c.moveToFirst()) {
do {
Area o1 = new Area();
o1.setOrderName(c.getString(1) + c.getString(2));
m_areas.add(o1);
} while (c.next());
}
db.close();
We check if the function returned a cursor at all, then move to the beginning of the cursor and start looping, going to the next item each time through. For more information on the Cursor interface see the API here, or to learn more about database access and related design practices better in general I suggest going through the Notepad tutorial.

Categories

Resources