I have some contacts autocomplete and autosearch algorithm working for my android app.
First some xml to define the text view for the input:
<AutoCompleteTextView
a:id="#+id/recipientBody"
a:layout_width="0dip"
a:layout_height="wrap_content"
a:layout_weight="1.0"
a:nextFocusRight="#+id/smsRecipientButton"
a:hint="#string/sms_to_whom"
a:maxLines="10"
/>
And now I setup the text view
AutoCompleteTextView recip =
(AutoCompleteTextView) findViewById(R.id.recipientBody);
ArrayAdapter<String> adapter =
new ArrayAdapter<String>(this, R.layout.list_item, getAllContacts());
recip.setAdapter(adapter);
And now the actual algorithm that searches for a contact that matches the input:
private List<String> getAllContacts() {
List<String> contacts = new ArrayList<String>();
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
if (Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
Cursor pCursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
new String[]{contactId}, null);
while (pCursor.moveToNext()) {
String phoneNumber = pCursor.getString(pCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contacts.add(phoneNumber + " ( " + displayName + " )");
}
pCursor.close();
}
}
}
return contacts;
}
This works fine for both contact number and name input. But there is still a problem. The user can input multiple phone numbers. But when one contact is applied to the text view it cannot search again, because the algorithm takes the whole string.
How can I solve that?
EDIT:
Well, I thought about it for a while and spotted a problem with my solution - there's no place where you could insert the contact from the completion list into the TextView.
The solution seems to be MultiAutoCompleteTextView, this thing is designed for solving your problem.
Sorry for confusion!
For me, it looks like you need a custom adapter.
You may extend ArrayAdapter<String> and implement getFilter() - of course you will also need a custom filter (extending Filter) which instance you will return from that method.
Filter's performFiltering method has one parameter - the string for which the list of suggestion is needed. You need to take the part after the last comma (or whatever character are you using as a separator) and return the suggestion list for that substring.
P.S.
For the better user experience, you may also think of styling your AutoCompleteTextView contents with Spans: http://ballardhack.wordpress.com/2011/07/25/customizing-the-android-edittext-behavior-with-spans/
Related
This AutoCompleteTextView was working fine, until I created a FilterQueryProvider and passed it to my Adapter's setFilterQueryProvider method. Basically what I'm trying to do is alow the user to input either a contact's name or phone number, and have the relevant contacts show up. Here's my relevant code:
//Set up the behavior for the recipient field.
AutoCompleteTextView destination = (AutoCompleteTextView) findViewById(R.id.destination_number);
//Get the list of contacts, add it to an array adapter.
/*
ArrayList<String>[] contactNames = getContactList();
ArrayList<String> contactNumbers = getContactNumbers(contactNames[1]);
*/
//Initialize the CursorAdapter.
final ContentResolver cr = getContentResolver();
Cursor nameCursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE},
ContactsContract.CommonDataKinds.Phone.HAS_PHONE_NUMBER + "> 0", null, null);
CompleteCursorAdapter mAdapter = new CompleteCursorAdapter(NewMessageActivity.this, nameCursor, false);
nameCursor.close();
FilterQueryProvider filter = new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence constraint) {
String query = "(instr(" + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME +
", '" + constraint + "') > 0 OR instr(" +
ContactsContract.CommonDataKinds.Phone.NUMBER
+ ", '" + constraint + "') > 0) AND " + ContactsContract.CommonDataKinds.Phone.HAS_PHONE_NUMBER + "> 0";
return cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE},
query, null, null);
}
};
mAdapter.setFilterQueryProvider(filter);
destination.setAdapter(mAdapter);
destination.setThreshold(2);
CompleteCursorAdapter is just a simple extended version of a CursorAdapter, and so far it's been working fine. I'm assuming that since I only have a few contacts on the virtual devices I'm testing on, and 100 or so on my actual phone, I'm thinking that the query is simply taking too long, and it's getting killed by the system.
However, even if I'm correct in this line of thinking, I'm not sure what I can do to solve this. Any help would be much appreciated!
Well, this was strange. It turns out that my phone must be running an older version of SQLite, since it didn't know what to do with instr(). I was able to work around this using LIKE statements.
I have a SearchView with a SimpleCursorAdapter, which queries suggestions from a SQLite DB. The query works just fine and the SearchView pops up a list with suggestions. However the suggestions are not visible. For example, if there are two suggestions, one can see that there are two list entries, but the text is not displayed. The SearchView is contained in a DialogFragment.
This is the initialization of the cursor adapter and the search view:
suggestionAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_1,
null,
new String[]{"name"},
new int[]{android.R.layout.simple_list_item_1},
0);
final SearchView searchProd = (SearchView) dialogView.findViewById(R.id.searchProd);
searchProd.setOnQueryTextListener(this);
searchProd.setOnSuggestionListener(this);
searchProd.setSuggestionsAdapter(suggestionAdapter);
Search suggestions are generated in onQueryTextChanged, where the new cursor is generated and injected to the suggestionsAdapter:
#Override
public boolean onQueryTextChange(String newText) {
String[] columns = new String[]{"_id", "name"};
String selection = "name LIKE ?";
String[] selectionArgs = new String[]{newText + "%"};
Cursor c = db.query(TABLE_NAME,
columns,
selection,
selectionArgs,
null,
null,
null);
if (c.getCount() > 0) {
suggestionAdapter.changeCursor(c);
suggestionAdapter.notifyDataSetChanged();
return true;
} else {
return false;
}
}
The issue is that the adapter will show data with column names such as SUGGEST_COLUMN_TEXT_1. This is the column displayed in the search dropdown box. But your cursor uses the database column name *name" iin your case. So what you have to do is tell the adapter to match your database column with your adapter column. The way that I do this is create a HashMap with something like hashMap.put(name, name + " as " SearcgManager.SUGGEST_COLUMN_TEXT_1) then in SQLiteBuilder mSqliteBuilder yoou can do mSqliteBuilder.setProjectionMap(hashMap) prior to creating your cursor. I don't use the onQueryTextChange for search suggestions but use contentprovider along with searchable configuration file. But because the columns are not matched is whyI believe you are not seeing your suggestions in your list
The problem is that you provided a layout for the to field in the constructor for SimpleCursorAdapter(). You should instead provide a resource ID for a textview, such as android.R.id.text1.
I had a similar problem. It turned out that all I needed to do is to change the color of the text that is displayed in the listed entries. So I have it something like
<TextView
android:id="#+id/item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFF2FF"
/>
I need to create a MultiAutoCompleteTextView with the phone numbers of the contacts on a user's device. What I need is similar to gmail; except with gmail email addresses are used. For the contacts, I have the following needs:
each phone number must be an entry. So if John has 3 numbers (home, cell, work), they show as 3 entries
each entry is searchable by phone number or by first/last name of person
To create my adapter, I try to modify the one provided by Google but when I download the sample, it does not compile (kind of a crappy experience when the thing is right out of the box, but I am trying troubleshoot it). Then using the sample at http://developer.android.com/reference/android/widget/MultiAutoCompleteTextView.html I will bound my MultiAutoCompleteTextView to the adapter. At this point, I am not sure how to convert the adapter to match my needs (i.e. search contacts by name or phone and to retrieve the numbers). So my call for help is this: has anyone successfully done this and don't mind sharing their code? Or Does anyone know how I can modify the linked adapter to give me phone numbers, which I can search by name or phone? And third, will the adapter work with MultiAutoCompleteTextView?
Note
In asking this question, I have made certain assumptions on how Google is implementing their MultiAutoCompleteTextView for emails. Does anyone know if that code is open source? Does anyone know if my assumptions are correct? Will my idea for implementing my contact phone MultiAutoCompleteTextView work?
UPDATE
So I have come a long way since asking the question. I am now using the answer at AutoComplete with name and number as in native sms app Android . But I am trying to convert the implementation to MultiAutoCompleteTextView but it's not allowing for multiple entries. Does anyone know how I might finish this?
UPDATE 2
Refer to AutoComplete with name and number as in native sms app Android :
My MultiAutoCompleteTextView is presently kind of working: it's allowing for multiple entries. I simply replaced AutoCompleteTextView with MultiAutoCompleteTextView, and I ignored the other answer's onItemClick suggestion. It's working, kind of. Except, the data I get is not the nice formatted elements that you see in the gmail EditText. So back to the original question: how is Google doing it? I don't want to spend time explaining how the gmail compose editText looks as the relevant reader can readily verify this. In their EditText I can enter four contacts and then with random access click on one to delete it. I want to be able to do that. How?
try this:
final Resources res = getResources();
LinearLayout ll = new LinearLayout(this);
AutoCompleteTextView tv = new AutoCompleteTextView(this);
tv.setThreshold(1);
String[] from = { Phone.DISPLAY_NAME };
int[] to = { android.R.id.text1 };
SimpleCursorAdapter a = new SimpleCursorAdapter(this, android.R.layout.simple_dropdown_item_1line, null, from, to, 0);
a.setStringConversionColumn(2); // Phone.NUMBER
ViewBinder viewBinder = new ViewBinder() {
#Override
public boolean setViewValue(View v, Cursor c, int index) {
TextView tv = (TextView) v;
int typeInt = c.getInt(3); // Phone.TYPE
CharSequence type = Phone.getTypeLabel(res, typeInt, null);
// Phone.DISPLAY_NAME + Phone.NUMBER + type
tv.setSingleLine(false);
tv.setText(c.getString(1) + "\n" + c.getString(2) + " " + type);
return true;
}
};
a.setViewBinder(viewBinder);
FilterQueryProvider provider = new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence constraint) {
// run in the background thread
Log.d(TAG, "runQuery constraint: " + constraint);
if (constraint == null) {
return null;
}
ContentResolver cr = getContentResolver();
Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, constraint.toString());
String[] proj = { BaseColumns._ID, Phone.DISPLAY_NAME, Phone.NUMBER, Phone.TYPE, };
return cr.query(uri, proj, null, null, null);
}
};
a.setFilterQueryProvider(provider);
tv.setAdapter(a);
ll.addView(tv, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
setContentView(ll);
I'm trying to fetch all the Phone numbers and Emails in Android.by using this code.
enter code here
String KEY_NAME = "Name";
String KEY_NO = "No";
String selection = ContactsContract.CommonDataKinds.Phone.IN_VISIBLE_GROUP + " = 1";
String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
String data="";
String name="";
ContactEntry contactObj;
String id;
List<String> temp = new ArrayList<String>();
final String[] projection = new String[]{ContactsContract.Contacts._ID , ContactsContract.Contacts.DISPLAY_NAME , ContactsContract.Contacts.HAS_PHONE_NUMBER};
final String[] email_projection = new String[] {ContactsContract.CommonDataKinds.Email.DATA , ContactsContract.CommonDataKinds.Email.TYPE};
final String[] phone_projection = new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE};
ContentResolver cr = context.getContentResolver();
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI , projection , selection , null , sortOrder);
if(cur.getCount()>0){
while(cur.moveToNext()){
id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
if (Integer.parseInt(cur.getString(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
// get the phone number
Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI , phone_projection ,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?",new String[]{id}, null);
while (pCur.moveToNext()){
data = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
if(!temp.contains(data) && !data.equals(null)){
}
}
pCur.close();
}
Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, email_projection,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", new String[]{id}, null);
while (emailCur.moveToNext()){
data = emailCur.getString(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
if(!temp.contains(data) && !data.equals(null)){
}
}
emailCur.close();
}
}
This code is working fine. but for the large number number of contacts let's say 5000 contacts then it blocks the UI thread.how to create a ListAdapter for displaying all these contacts.If i fetch all the contacts in background user will see the empty list for a long time.please suggest some solution
I had very similar problem some time ago even with significantly lower number of contacts.
I needed to populate all contacts in list view and allow the user to select from them. Initially I was loading all the contact information in the list view. However this required really a lot of queries, which is what actually is slow.
So I changed my design: I selected only the Contact name and the Contact id and recorded it in an object. Afterwards when the user of my app selected any contact I loaded only his data. This turned to be drastically faster (as expected). And in my case it worked perfectly, because I was querying a lot of information which I actually never needed (that is phone numbers and emails of all not-selected contacts).
Hopefully you will be able to redesign your app in similar way. However if you need to display the contents of the data variable in the listview right away, you really might turn to need lazy-loading list view with adapter (lets just hope it will perform smoothly even on fast scroll).
I query and get a result set back, but I need to do some calculations that are impossible in the SQLite WHERE clause in order to determine what shows up in the ListView. How can I remove certain rows from the cursor? I know it is the same question as this Filter rows from Cursor so they don't show up in ListView but that answer does not help. Can an example be provided if there isn't a simpler way to do this?
It might work to simply retain all the rows in the Cursor, but then use a custom adapter to hide the unwanted rows at display time. For example, if you extend CursorAdapter, then you might have something like this in your bindView implementation:
View v = view.findViewById(R.id.my_list_entry);
boolean keepThisRow = .......; // do my calculations
v.setVisibility(keepThisRow ? View.VISIBLE : View.GONE);
There should be a better way to do this, but what I ended up doing is storing the ID of each row I wanted in a string ArrayList, and then requerying where _id IN arraListOfIds.toString(), replacing the square brackets with parentheses to fit SQL syntax.
// Get all of the rows from the database
mTasksCursor = mDbHelper.fetchAllTasks();
ArrayList<String> activeTaskIDs = new ArrayList<String>();
// calculate which ones belong
// .....
if (!hasCompleted)
activeTaskIDs.add(mTasksCursor.getString(TaskerDBadapter.INDEX_ID));
// requery on my list of IDs
mTasksCursor = mDbHelper.fetchActiveTasks(activeTaskIDs);
public Cursor fetchActiveTasks(ArrayList<String> activeTaskIDs)
{
String inClause = activeTaskIDs.toString();
inClause = inClause.replace('[', '(');
inClause = inClause.replace(']', ')');
Cursor mCursor = mDb.query(true, DATABASE_TABLE, columnStringArray(),
KEY_ROWID + " IN " + inClause,
null, null, null, null, null);
if (mCursor != null) { mCursor.moveToFirst(); }
return mCursor;
}
ContentResolver cr = getContentResolver();
Cursor groupCur = cr.query(
Groups.CONTENT_URI, // what table/content
new String [] {Groups._ID, Groups.NAME}, // what columns
"Groups.NAME NOT LIKE + 'System Group:%'", // where clause(s)
null, // ???
Groups.NAME + " ASC" // sort order
);
The "What Columns" piece above is where you can tell the cursor which rows to return. Using "null" returns them all.
I need to do some calculations that
are impossible in the SQLite WHERE
clause
I find this very hard to believe; my experience has been that SQL will let you query for just about anything you'd ever need (with the exception of heirarchical or recursive queries in SQLite's case). If there's some function you need that isn't supported, you can add it easily with sqlite_create_function() and use it in your app. Or perhaps a creative use of the SELECT clause can do what you are looking for.
Can you explain what these impossible calculations are?
EDIT: Nevermind, checking out this webpage reveals that the sqlite_create_function() adapter is all closed up by the Android SQLite wrapper. That's annoying.