Android SearchView with custom suggestions adapter - android

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"
/>

Related

AutoCompleteTextView with SimpleCursorAdapter for Contacts not returning cursor actual value

I have a AutoCompleteTextView that uses a SimpleCursorAdapter to filter emails for an input field. I have it working, although there are some deprecated commands that I am not sure how to rework.
The only problem I am having is that when I select a value from the list provided, I am not getting the email address selected, but something like the following:
android.content.ContentResolver$CursorWrapperInner#13a08d9c
Here is the code I have:
final AutoCompleteTextView edt_Contact = (AutoCompleteTextView)findViewById(idTo);
ContentResolver cr = getContentResolver();
String[] projection={ContactsContract.CommonDataKinds.Email._ID,ContactsContract.CommonDataKinds.Email.ADDRESS};
Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, null, null, null);
startManagingCursor(cursor);
String[] from = new String[] { ContactsContract.CommonDataKinds.Email.ADDRESS};
int[] to = new int[] { android.R.id.text1};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor, from, to);
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
return getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
new String[] {},
ContactsContract.CommonDataKinds.Email.ADDRESS + " LIKE '%" + constraint + "%'",
null, null);
}
});
edt_Contact.setAdapter(adapter);
Any suggestions on how to get the actual selected value to populate the AutoCompleteTextView when selected?
Also, as mentioned, the deprecated items are startManagingCursor and SimpleCursorAdapter.
Found the solution and I am posting it here for anyone else with a similar situation
I needed to add the following
adapter.setStringConversion(1);
I added it right before the last line in the example above. This changed the result from the
android.content.ContentResolver$CursorWrapperInner#13a08d9c
to the selected email address.
Just a heads-up too for anyone using the MultiAutoCompleteTextView to do the same thing, this code works for that as well... just change add Multi before the AutoCompleteTextView and add the .setTokenizer of your choice.

Android - populate ListView SQLite, cursor null pointer

I have two tables atm, users and notes. I am trying to retrieve data that belongs to the user. So all data to list must be owned by the original user and shown only to him. I have made my table in Databasehelper.
I have made a new class that controls the notes table. In listNotes() I want to loop through the cursor row and get all data owned by the user. Am I quering it correctly?
// Listing all notes
public Cursor listNotes() {
Cursor c = db.query(help.NOTE_TABLE, new String[]{help.COLUMN_TITLE,help.COLUMN_BODY, help.COLUMN_DATE}, null, null, null, null, null);
if (c != null) {
c.moveToFirst();
}
db.close();
return c;
}
I then want to display the cursor data collected in a listview
public void populateList(){
Cursor cursor = control.listNotes();
getActivity().startManagingCursor(cursor);
//Mapping the fields cursor to text views
String[] fields = new String[]{help.COLUMN_TITLE,help.COLUMN_BODY, help.COLUMN_DATE};
int [] text = new int[] {R.id.item_title,R.id.item_body, R.id.item_date};
adapter = new SimpleCursorAdapter(getActivity(),R.layout.list_layout,cursor, fields, text,0);
//Calling list object instance
listView = (ListView) getView().findViewById(android.R.id.list);
adapter.notifyDataSetChanged();
listView.setAdapter(adapter);
}
You aren't creating the NOTE_TABLE right.
You miss a space and a comma here
+ COLUMN_DATE + "DATETIME DEFAULT CURRENT_TIMESTAMP"
It has to be
+ COLUMN_DATE + " DATETIME DEFAULT CURRENT_TIMESTAMP,"
There are two issues here:
One is you have missed a comma (after the Timestamp as specified in an earlier answer).
The other error you have is when using a SimpleCursorAdapter, you need to ensure that the Projection string array includes something to index the rows uniquely and this must be an integer column named as "_id". SQLite already has a feature built in for this and provides a column named "_id" for this purpose (however you can have your own integer column which you can rename to _id). To solve this, change your projection string array to something like:
new String[] {"ROW_ID AS _id", help.COLUMN_TITLE,help.COLUMN_BODY, help.COLUMN_DATE}
I guess the NullPointerException stems from this (but without the stacktrace I don't know for sure).

Android TextView autocomplete and autosearch

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/

android spinner default value using cursor adapter

I am using a spinnerbox in my application. The spinnerbox is to be filled with projects from the database. This itself already works. However i need one extra item in the drop down list.
I want the first item to be "general" general is not a project. Thus it is not retrieved from the database. Is there someway to either inject it in thye cursor or adapter?
What worked for me was to do a UNION in the sql query.
dbStatic.rawQuery(
" SELECT 2 as deftop, typeid as _id, typename as label FROM objtypes UNION "+
" SELECT 1 as deftop, -1 as _id, "+strDefaultSpinner+" as label "+
" ORDER BY deftop asc, label ", null
);
if the item selected is -1, then it's the default value. Otherwise it's a record from the table.
I encountered the same problem a while ago ..
the problem is that you cant actually insert information into a cursor (because its just a pointer) so I believe you have to have some kind of mediator in between ..
my way of solving it was to simply crate a string array [cursor.getCount+1]
then insert your "general" in [0] and then go through your cursor to insert the rest ..
it does go through the items an extra round (which isn't so bad in my case) but for a long list you might want to override the adaptar and insert a line before it goes through the cursor which i cannot help you with the code for that..
I managed to solve this in a differebt way then i originally planned. But it works well. Instead of a general option. I made a checkbox. Is it checked, then its general and the spinner is setunabled. And if unchecked it gets set to enabled. This works for my situation.
My example is working with androidx, room and spinner component.
In your content provider you should have something like.
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection,
#Nullable String[] selectionArgs, #Nullable String sortOrder) {
...
String all = context.getResources().getString(R.string.search_spinner_all);
SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT '"+all+"' as "+ThemeData.COLUMN_ID+",'' as "+ThemeData.COLUMN_SET_COUNT+",0 as "+ThemeData.COLUMN_SUBTHEME_COUNT
+",0 as "+ThemeData.COLUMN_YEAR_FROM+",0 as "+ThemeData.COLUMN_YEAR_TO
+" UNION SELECT * FROM " + ThemeData.TABLE_NAME+" ORDER BY "+ ThemeData.COLUMN_ID);
cursor = themeDataDao.selectAll(query);
...
}
In your dao, use
#RawQuery
#Dao
public interface ThemeDataDao {
#RawQuery
Cursor selectAll(SupportSQLiteQuery query);
}
You got it, you can use your simple implementation or cursor adapter !
themesAdapter = new SimpleCursorAdapter(getContext(), R.layout.spinner_with_count, null,
new String[]{ThemeData.COLUMN_ID, ThemeData.COLUMN_SET_COUNT}, new int[] { R.id.spinnerTxLabel, R.id.spinnerTxCount }, 0);
inputTheme.setAdapter(themesAdapter);
LoaderManager.getInstance(this).initLoader(LOADER_THEMES, null, themesLoaderCallback);

Removing rows from an Android SQLite Cursor

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.

Categories

Resources