I have an actionbar SearchView that I'm trying to get working to let users search a database of wines.
I'm trying to get it to search while the user types, like if they type "na" it'll show all results that contain "na" and so on.
Here is my menu search button if statement:
if (id == R.id.action_wine_search) {
SearchView searchView = getActivity().findViewById(R.id.action_wine_search);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String newText) {
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
mCursorAdapter.getFilter().filter(newText);
//mCursorAdapter is selecting id,name,price,etc. columns from the table
wineListView.setAdapter(mCursorAdapter);
return false;
}
});
}
As I understood it (which I might just be thinking about this in the wrong way) is that the filter is getting the text from the SearchView and is applying that text to the CursorAdapter.
This isn't doing anything though. The list doesn't change at all no matter what you type in.
I followed along to a youtube video for this. I don't quite understand how it was applying to the CursorAdapter though.
I ended up attempting it on my own (this also fails, the list doesn't change at all):
public boolean onQueryTextChange(String newText) {
SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
//Selecting what details I want to put into each row in the list
String[] projection = {
WineContract.WineEntry.COLUMN_ID,
WineContract.WineEntry.COLUMN_WINE_NAME,
WineContract.WineEntry.COLUMN_WINE_PRICE,
WineContract.WineEntry.COLUMN_WINE_RATING,
WineContract.WineEntry.COLUMN_WINE_STORE,
WineContract.WineEntry.COLUMN_WINE_IMAGE,
WineContract.WineEntry.COLUMN_WINE_THUMBNAIL};
//I want to return the row where the name is equal to newText (which is what the user types into the SearchView
String selection = WineContract.WineEntry.COLUMN_WINE_NAME + " = ?";
String[] selectionArgs = {newText};
Cursor cursor = db.query(WineContract.WineEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
mCursorAdapter = new WineCursorAdapter(getActivity(), cursor);
wineListView.setAdapter(mCursorAdapter);
return false;
}
});
Could I get some help in understanding how this is supposed to work?
I looked at the video you linked and unfortunately, it looks like the video demonstrates how to achieve this search functionality in a pretty different approach. Namely, they use an ArrayAdapter<String> that has some built-in implementation of a Filter that allows them perform substring matching on a simple list of results.
Your case of using a CursorAdapter to search against a database unfortunately won't work in this manner. A CursorAdapter returns a Cursor object which is really just an "facade" for retrieving the row data/results returned by a database query. In other words, the CursorAdapter doesn't know how to "filter" anything, you have to specify what to do when a filter is applied. For instance, you would need to implement something like this:
adapter.setFilterQueryProvider(new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence query) {
// Run some database query, given this "query" String and return a Cursor
return searchDatabase(query);
}
});
You sort of had the right idea your second snippet of code to implement a search query. However, a standard SQLite database table is only going to let you retrieve rows that match an exact criteria (basically what you've already coded above) and not a substring match like what you probably want.
Fortunately, there is a way to get full-text search in an SQLite database so you can search and match queries to substrings and partial queries, probably what you actually want. You can use something called an SQLite virtual table that implements a scheme like FTS3 to support searching. There is a great tutorial on the official Android docs linked below that will show you exactly how to achieve this:
https://developer.android.com/training/search/search.html
I've used this method in the past several times to implement searching across a database. It's probably a bit more work than you expected to change out your current database helper but this is the way I know how to do it. Let me know if anything doesn't make sense!
Also I do you hope take into account my comments above about restructuring your code because I'm sure you want to write an app that considers good performance!
I'm not sure if this would suit be all I do for a similar is have an EditText with a textChangedListener that just drives a a query, gets an updated cursor and uses swapCursor on the ListView adapter. The query itself uses Like to get all occurences e.g. that have na.
This the Listener setup Code :-
/**************************************************************************
* addFilterListener - Add a Listener to filter the ListView
* i.e. as characters are typed then the ListView
* is filtered according to the characters input
* This is done by using LIKE '%<characters>%'
*
*/
public void addFilterListener() {
inputproductfilter.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
productfilter = DBProductsTableConstants.PRODUCTS_NAME_COL_FULL +
SQLLIKECHARSTART +
inputproductfilter.getText().toString() +
SQLLIKECHAREND;
plcsr = dbproductmethods.getExpandedProducts(productfilter,orderby);
productlistadapter.swapCursor(plcsr);
}
#Override
public void afterTextChanged(Editable editable) {
}
});
}
It uses :-
public static final String SQLLIKECHARSTART = " LIKE '%";
public static final String SQLLIKECHAREND = "%' ";
and for below
public static final String SQLAS = " AS ";
Not that it's all that relevant here's getExpandedproducts :-
/**************************************************************************
*
* #param filter filter string less WHERE keyword
* #param order order String less ORDER and BY kewords
* #return Cursor containing Expanded products
*/
Cursor getExpandedProducts(String filter, String order) {
Cursor rv;
String[] columns = new String[] {
DBProductsTableConstants.PRODUCTS_ID_COL_FULL,
DBProductsTableConstants.PRODUCTS_NAME_COL_FULL,
DBProductsTableConstants.PRODUCTS_NOTES_COL_FULL,
DBProductsTableConstants.PRODUCTS_STORAGEORDER_COL_FULL,
DBProductsTableConstants.PRODUCTS_STORAGEREF_COL_FULL,
DBStorageTableConstants.STORAGE_ID_COL_FULL +
SQLAS + DBStorageTableConstants.STORAGE_ALTID_COL,
DBStorageTableConstants.STORAGE_NAME_COL_FULL,
DBStorageTableConstants.STORAGE_ORDER_COL_FULL
};
String table = DBProductsTableConstants.PRODUCTS_TABLE +
SQLLEFTJOIN +
DBStorageTableConstants.STORAGE_TABLE +
SQLON +
DBProductsTableConstants.PRODUCTS_STORAGEREF_COL_FULL +
" = " +
DBStorageTableConstants.STORAGE_ID_COL_FULL;
rv = db.query(table,columns,filter,null,null,null,order);
return rv;
}
e.g. full list :-
and then with na entered in the filter :-
Related
I hope straightforward questions.
1) I managed to get the data from Sqlite db and showing them on recyclerview. The question is for example when i click on the recyclerview items and do some operations (for example copying the content or updating) is it better to use an arraylist and get the data first when application loads then do the operations on this arraylist elements (then notifying db eventually)?
2) If there is no need for extra arraylist on onContextItemSelected() operations while clicking recyclerview item again, i ve some trouble in choosing the element and its values.
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.idshare :
//implicit intent
shareImplicitIntent();
return true;
......
for the shareImplicitIntent() method
private void shareImplicitIntent() {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
Cursor cursor=WordListOpenHelper.mReadableDB.rawQuery("SELECT * FROM
diary", null);
cursor.moveToPosition(XYZ);
Entry_Model entry_model= new Entry_Model();
entry_model.setmEntry(cursor.getString(cursor.getColumnIndex(WordListOpenHelper.KEY_ENTRY)));
String title = entry_model.getmEntry(); ......
basically using cursor and getting the title of the cursor at XYZ position.
But how can I choose that XYZ position ?
Working hours on it but couldnt find a clue. Please help me.Thanks a lot
To answer my question myself, shortly no, for example for getting input from the user and putting them in the arraylist then doing the database operations on the arraylist not very useful nor necessary. (Yet if your database is planned to hold only small amount of entries though you can use arraylist/linkedlists for fast CRUD manipulations on the recyclerview adapter).
For the second part of the question it s easy to copy the content of the clicked recyclerview element by creating setonclicklistener in the viewholder constructor of the viewholder innerclass, for example;
(note unlike in the example you dont have to use contentresolver if you dont plan to share the datas in the database with other applications)
itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
int pos =getAdapterPosition();
String entry = "";
String[] mProjection =
{ Contract.WordList.KEY_ENTRY, // Contract class constant for the _ID column name };
Cursor cursor = mContext.getContentResolver().query(Uri.parse(
queryUri), mProjection, null, null, sortOrder);
if (cursor != null) {
if (cursor.moveToPosition(pos)) {
int indexEntry = cursor.getColumnIndex(Contract.WordList.KEY_ENTRY);
entry = cursor.getString(indexEntry);
}
}
Toast.makeText(v.getContext(), "copied entry is " + entry, Toast.LENGTH_LONG).show();
return false;
}
});
I’m using the sample provided and have tweaked the code to my requirement. I’m learning Rx and not completely familiar with how it works. So, in my fragment I have
private static String LIST_QUERY = "SELECT * FROM " + TodoItem.TABLE;
And my onResume looks like:
#Override
public void onResume() {
super.onResume();
subscription = db.createQuery(TodoItem.TABLE, LIST_QUERY)
.mapToList(TodoItem.MAPPER)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter);
}
The adapter showed the data in a listview and it works fine but now I have added a TextView on top of the list and want to filter results whenever the user enters text. I believe RxAndroid provides a debounce operator to make this efficient (and not query the DB instantly) but I’m unsure how I can combine this subscription code to listen to the textView changes and filter according to that text.
I already tried changing LIST_QUERY to "SELECT * FROM " + TodoItem.TABLE + " WHERE " + TodoItem.DESCRIPTION + " LIKE \"" + query + "\" LIMIT 5”;
and then in tried:
etSearch.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence searchQuery, int i, int i1, int i2) {
query = searchQuery.toString();
adapter.notifyDataSetChanged();
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { //ignore }
#Override
public void afterTextChanged(Editable editable) { //ignore }
});
This resulted in the list being empty (I thought this was okay since the initial query is empty string) but when I typed some text the query didn’t update/filter the list at all.
I tried an alternate approach and added take(5) (since I want LIMIT 5) after mapToList and that worked and showed only 5 items. And then I added .filter(, typed new and let Android Studio generate the Func1 code and it looked like:
subscription = db.createQuery(ListItem.TABLE, LIST_QUERY)
.mapToList(Item.MAPPER)
.filter(new Func1<List<ListItem>, Boolean>() {
#Override
public Boolean call(List<ListItem> items) {
return null;
}
})
So problem is that it asks me to filter the whole list. I tried this:
public Boolean call(List<ListItem> items) {
for(ListItem i: items)
if(!i.description().startsWith(query))
items.remove(i);
return true;
}
But still the entire list shows and the changes in the text do not make the list change at all. I believe I missed something, that I have to subscribe to the EditText but I’m not sure how I would do this and combine it with this existing database subscribe code. Looking for solutions.
There's a few different ways you can do this. If you want the filter to exist in java then you can use the asRows operator on QueryObservable:
database.createQuery(ListItem.TABLE, ListItem.QUERY)
.flatMap(query -> query.asRows(Item.MAPPER)
.filter(item -> item.description.startsWith(query))
.toList())
This will be an Observable<List<Item>> with only items that match the query.
However your original approach is the ideal way to do it, you were just using LIKE incorrectly. You probably want to include wildcards (% or _) to include results that dont match the query exactly. For example:
"SELECT * FROM " + TodoItem.TABLE + " WHERE " + TodoItem.DESCRIPTION + " LIKE \"%" + query + "%\" LIMIT 5”;
will match any results where the query is contained in the description. The % wildcard matches 0 or more of any character. The _ wildcard matches a single character.
The other part you mentioned is that the list isn't updating properly. You want to use RxAndroid here and not textChangedListeners most likely. In your view you will have something like
RxTextView.textChanges(editText)
.switchMap(query -> database.createQuery(ListItem.TABLE, ListItem.query(query))
.mapToList(ListItem.MAPPER))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(list -> {
adapter.setData(list);
adapter.notifyDataSetChanged();
});
This way you're not worrying about any races between the query executing and the adapter getting the notify call. The ListItem.query(query) method just composes that sqlite string with the query.
I have two AutoCompleteTextView (group and instalation).
After I choose group, Instalation should filter the information depending on the group but it's not working.
If in the sql line i take out the WHERE part it works fine (but shows me all the information and i don't want that).
List<String> instalacao = new ArrayList<String>();
Cursor cursor=mydb.rawQuery("SELECT DISTINCT instalacao FROM registo WHERE grupo like '"+txGrupo.getText().toString()+"';",null);
while (cursor.moveToNext()){
instalacao.add(cursor.getString(cursor.getColumnIndex("instalacao")));
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.select_dialog_item,instalacao);
txInstalacao.setThreshold(0);
txInstalacao.setAdapter(adapter);
The dropdown:
txInstalacao.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
txInstalacao.showDropDown();
}
});
What am I doing wrong?
If you want to match a prefix, you should use '%' at the end of the parameter.
"WHERE grupo like '" + txGrupo.getText().toString() + "%'"
As an aside, I would strongly recommend using the query() method instead of rawquery(). Building an SQL statement like this will lead to problems (for example if the contents of txGrupo contains quotes).
I figured out why it wasnt showing anything. When i start the intent i call the method, at this time the TextField is empty so it has nothing there. when i choose my group i have to refresh the method and i was not doing that :)
With 3.0 we got the fancy LoaderManager, which handles data loading using the AsyncTaskLoader, the CursorLoader, and other custom Loader instances. But reading through the docs for these I just couldn't get the point: how are these better than just using the good old AsyncTask for data loading?
Well they are a lot simpler to implement, and take care of everything about lifecycle management so are much less error prone.
Just look at the sample code, for showing the result of a cursor query which lets the user interactively filter the result set through a query input field in the action bar:
public static class CursorLoaderListFragment extends ListFragment
implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
#Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No phone numbers");
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null,
new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add("Search");
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
}
public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
#Override public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}
#Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
Correctly implementing this full example yourself with AsyncTask is going to involve a lot more code... and even then, are you going to implement something as complete and well working? For example, will your implementation retain the loaded Cursor across activity configuration changes so it doesn't need to be re-queried when the new instances is created? LoaderManager/Loader will do that automatically for you, as well as taking care of correctly creating and closing the Cursor based on the activity lifecycle.
Also notice that using this code doesn't require that you think at all about making sure long running work is performed off the main UI thread. LoaderManager and CursorLoader take care of all of that for you, ensuring you will never block the main thread while interacting with the cursor. To do this correctly you actually need to have two Cursor objects active at the same time at points, so you can continue to display an interactive UI with your current Cursor while the next one to show is being loaded. LoaderManager does all of that for you.
This is just a much simpler API -- no need to know about AsyncTask and think about what needs to run in the background, no need to think about activity lifecycle or how to use the old "managed cursor" APIs in Activity (which didn't work as well as LoaderManager anyway).
(Btw don't forget the new "support" static library that let you use the full LoaderManager API on older versions of Android down to 1.6!)
I will like to know if we can continuously call some service for fetching results and displaying in Autocomplete list.
I have one screen with the text box and when user starts entering in that textbox the autocomplete should get filled with the data. The data will not be hardcoded and will be fetched through http connection. I think I need to call http connection in onTextChanged method of Edittext but is that the perfect solution.
Moreover, should this type of implementation done in mobile application. Since, this feature is web based. Can this be done in mobile application too?
Is this feasible?
Write a custom SimpleCursorAdapter. Now associate this adapter to your EditText. Here is the code to construct a Cursor object and return it:
public class ValueCursorAdapter extends SimpleCursorAdapter implements Filterable
{
...
// overrise the newView() to associate mCursor[1] and mCursor[2] to relevant views within
...
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint)
{
MatrixCursor mCursor = new MatrixCursor(new String[] { "_id", "uri", "label" });
.. // result = ??
while (result.hasNext())
{
mCursor.addRow(new Object[] { count, "uri", "title"});
count++;
}
return mCursor;
}
}
Here is an example for Customizing Cursor Adapter. You might need to customize it to fit your requirements.