Injecting a column in content provider - android

I am using custom built content provider to pull data from my database. There can be many records(like 10k) so instead of implementing some mechanism of lazy loading to a list, I took a chance and created content-provider as it has internally a mechanism for "lazy loading". All works well.
I need to add a field (column) which should not go in database, but it is a selector, a check-box. Say this is a list of some users loading from DB, and I want to create something close to WhatsApp "add contacts to broadcast" (see image)
The code for content provider is nothing special:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
................
Cursor cursor = customContactsDataBase.getContacts(id, projection, selection, selectionArgs, sortOrder);
return cursor;
}
And getContacts is a simple SQLiteQueryBuilder returning a cursor.
So, my question is, should I inject this new check-box column,
if yes, then where to keep the values (in a separate "matching by id"
list or a map and dynamically fill the values)
Or should I use some other mechanism ?

I've done something similar to this, a shopping list with favourites where you check the favourites to add them to your list.
I considered it a multi select context action bar (CAB), just forced permanently into the context action bar state. So I applied the same method I use for CABs.
In my adapter I add an array to hold checked items
private SparseBooleanArray checkedItems = new SparseBooleanArray();
I have public method in the adapter to toggle a check, get a list of all checked items and a few support methods as below.
public void toggleChecked(int pos) {
Log.d(TAG, "Position " + pos);
if (checkedItems.get(pos, false)) {
checkedItems.delete(pos);
} else {
checkedItems.put(pos, true);
}
notifyItemChanged(pos);
}
public boolean isChecked(int pos) {
return checkedItems.get(pos, false);
}
public void clearChecked() {
checkedItems.clear();
notifyDataSetChanged();
}
public int getCheckedItemCount() {
return checkedItems.size();
}
public List<Integer> getCheckedItems() {
List<Integer> items = new ArrayList<>(checkedItems.size());
for (int i = 0; i < checkedItems.size(); i++) {
items.add(checkedItems.keyAt(i));
}
return items;
}
You then just need to wire up your viewholder check event to go and update the adapter (I bubble the check event right up to the activity level then back into the adapter as I found it the easiest method with other validation required but that specific to my implementation).

Related

Updating the SQLite DB with a Button click on listView

I am trying to build a simple stock application, I have a list view on my main activity which has a "Sell" button on every list item I have. The functionality of the Sell button should decrease the quantity of that particular item by updating the row for that item and setting the quantity to quantity-1.
To achieve this, I have found that setting up an on click listener in my custom cursor adapter class was the way to do it. I am using a content provider class for my Database operations. So what I tried to do is, trigger a function which is in my main activity, within the OnClickListener which is in my cursor adapter. Here is some code that would explain more. (please forgive my terrible programming skills, I am fairly new )
My approach does not seem to work for some reason, first click on Sell button does not do anything, and the second one crashes the application with the reason:
android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.
p.s. I did not send the context from the adapter to decrease count method, and it was crashing of a null pointer on the getContentResolver().
Update function in my content provider:
private int updateItem (Uri uri, ContentValues values, String selection, String[] selectionArgs){
if (values.containsKey(InventoryContract.ItemEntry.COLUMN_NAME)){
String name = values.getAsString(InventoryContract.ItemEntry.COLUMN_NAME);
if (name == null){
throw new IllegalArgumentException("Item requires a name");
}
}
// If values size is zero, do not try to update the database.
if (values.size() == 0){
return 0;
}
// Otherwise, get writeable database to update the data
SQLiteDatabase database = mDbHelper.getWritableDatabase();
// Perform the update on the database and get the number of rows affected
int rowsUpdated = database.update(InventoryContract.ItemEntry.TABLE_NAME, values, selection, selectionArgs);
// If 1 or more rows were updated, then notify all listeners that the data at the
// given URI has changed
if (rowsUpdated != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
// Return number of rows updated
return rowsUpdated;
}
The function I have written ( or tried to write ) in my main activity
public void decreaseCount(Context context, int columnId, int quantity){
quantity = quantity -1;
ContentValues values = new ContentValues();
values.put(InventoryContract.ItemEntry.COLUMN_QUANTITY, quantity);
Uri updateUri = ContentUris.withAppendedId(InventoryContract.ItemEntry.CONTENT_URI, columnId);
int rowsAffected = context.getContentResolver().update(updateUri, values,null, null);
}
and lastly, the custom OnClickListener I have added to the button (p.s. the listener is inside the overriden bindView method of the cursor adapter )
sellButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int columnIdIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry._ID);
int quantityIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry.COLUMN_QUANTITY);
CatalogActivity catalogActivity = new CatalogActivity();
catalogActivity.decreaseCount(context2, Integer.valueOf(mCursor.getString(columnIdIndex)), Integer.valueOf(mCursor.getString(quantityIndex)));
}
});
Thank you in advance !
The problem is very trivial. I fixed your codes. First don't create objects out of activities. Try to use boxing and unboxing technic to retrieve your context back. In your InsertCursorAdapter constructor should be like this
public ItemCursorAdapter(Context context, Cursor c) {
super(context, c);
this.context = context;
}
Then you need to save your cursor from bindView method.
Then you need to bind the context object to get your activity object back. All in all, you would have something like this:
#Override
public void bindView(View view, final Context context, Cursor cursor) {
this.mCursor = cursor;
TextView nameTextView = view.findViewById(R.id.name);
TextView quantityTextView = view.findViewById(R.id.quantity);
sellButton = view.findViewById(R.id.sell_button);
ImageView imageView = view.findViewById(R.id.item_image);
sellButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int columnIdIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry._ID);
int quantityIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry.COLUMN_QUANTITY);
String col= mCursor.getString(columnIdIndex);
String quan= mCursor.getString(quantityIndex);
CatalogActivity catalogActivity = (CatalogActivity) context;
catalogActivity.decreaseCount( Integer.valueOf(col), Integer.valueOf(quan));
}
});
Also I changed your decreaseCount arguments. Because this method is in activity class you don't need to pass it anytime you need to decrease the value. getContentResolver() method is a method in super class AppCompatActivity and because it is public, your activity have implemented it already.
//TODO: Decrease count by one
public void decreaseCount(int columnId, int quantity){
quantity = quantity -1;
ContentValues values = new ContentValues();
values.put(InventoryContract.ItemEntry.COLUMN_QUANTITY, quantity);
Uri updateUri = ContentUris.withAppendedId(InventoryContract.ItemEntry.CONTENT_URI, columnId);
int rowsAffected = getContentResolver().update(updateUri, values,null, null);
}

CursorLoader + CursorAdapter + Regular DB updates = Gridview Flicker

I have a custom image gallery app that stores the image data in a database. The gallery is a GridView backed by CursorAdapter.
When first requesting an image decode ContentResolver.update updates the height, width, and orientation in a background thread. Since many images are visible at a time the database updates are happening rapidly as a user scrolls which causes the backing CursorLoader to refresh the GridView rapidly which manifests itself as a rapid flicker.
Is it not possible to use CursorLoader and CursorAdapter if the underlying source can rapidly change, or am I missing something here?
When there's a change to monitored data (selection, projection) within a database the LoaderManager simply supplies a new cursor. As such, as far as the CursorAdapter is concerned it will have an entirely new datasource (swapCursor) on every update to the database:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
{
mGalleryAdapter.swapCursor(cursor);
}
Therefore the ideal CursorAdapter attached to a dynamic LoaderManager will need to micro-manage view updates to ensure that it does not bother updating views that are exactly the same. To do so (since my views are fairly complicated) I created a helper object to handle translations and comparisons between cursor and views (removed most views for the sake of brevity).
public static class GalleryItem
{
private String name;
private int rotation;
public static GalleryItem fromCursor(Context c, Cursor cursor)
{
GalleryItem item = new GalleryItem();
item.rotation = ImageUtils.getRotation(cursor.getInt(Meta.ORIENTATION_COLUMN));
item.name = cursor.getString(Meta.NAME_COLUMN);
return item;
}
public static GalleryItem fromViewHolder(ViewHolder vh)
{
GalleryItem item = new GalleryItem();
item.rotation = (int)vh.mImageView.getRotation();
item.name = (String) vh.mFileName.getText();
return item;
}
#Override
public boolean equals(Object o)
{
GalleryItem compare = (GalleryItem) o;
boolean sameRotation = rotation == compare.rotation;
boolean sameName = name == null ? compare.name == null : name.equals(compare.name);
return sameName && sameRotation;
}
}
Then use this to check if you need to update anything in the view:
#Override
public void onBindViewHolder(ViewHolder vh, final int position, Cursor cursor)
{
GalleryItem galleryItem = GalleryItem.fromCursor(mContext, cursor);
GalleryItem former = GalleryItem.fromViewHolder(vh);
// If nothing has changed avoid refreshing.
// The reason for this is that loaderManagers replace cursors meaning every change
// will refresh the entire data source causing flickering
if (former.equals(galleryItem))
return;
...
So long story short, with a dynamic LoaderManager you'll have to implement a way to skip recreating completely unchanged views.

How can i use the Check Boxes with ListView and Insert the values in Sqlite Database in android

I'm developing an android app which shows phone contact as ListView(used Cursoradapter).Now I Want to add checkbox to the listView ,My problem is How to Insert checkbox data into database, based on if it is checked or not?
In my database class, I have a function which use to add names and numbers to my database,
createntry(String number,String name) // in my database class
Should I invoke this function in my CursorAdapter class ?
Recently, I found out that I should use getView function,but unfortunately I have no idea about getView, My question are
1-I should use this function in My CursorAdapter or else?
2- how to implement this function?
My CursorAdapterClass
public class ContactCursorAdapterCT extends CursorAdapter {
public ContactCursorAdapterCT(Context context, Cursor c) {
super(context, c);
}
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView name = (TextView)view.findViewById(R.id.contactlistTV1);
name.setText(cursor.getString
(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
TextView phone = (TextView)view.findViewById(R.id.contactlistTV2);
phone.setText(cursor.getString
(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.lvct, parent, false);
bindView(v, context, cursor);
return v;
}
public View getView(final int pos, View inView, ViewGroup parent) { //getView
}
My activity class
public class Contacts extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.contacts);
Cursor cursor = getContentResolver().query
(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null,null, null);
startManagingCursor(cursor);
ContactCursorAdapterCT adapter= new ContactCursorAdapterCT
(Contacts.this, cursor);
ListView contactLV = (ListView) findViewById(R.id.listviewblcontactsDB);
contactLV.setAdapter(adapter);
My database Class
public long creatEntry(String inputnumber , String name) { // for add data
// TODO Auto-generated method stub
ContentValues cv= new ContentValues();
cv.put(KEY_NUMBER, inputnumber);
cv.put(N_NAME, name);
Log.v(inputnumber, "adding to Database");
return ourdatabase.insert(DATABASE_TABLE, null, cv);
}
Firstly, no you don’t need getView. bindView in conjunction with newView is completely sufficient as a replacement for it, some would probably say even better. Moreover, you don’t need to call bindview in new view. here’s a restructuring of what should be there.
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = inflater.inflate(R.layout.lvct, parent, false);
return v;
}
This is totally your call but no I don’t think that you should your createEntry method into the adapter, at least in its methods. The thing is that methods in the adapter are called for each row of your listview, so you might have a lot happening redundantly and plus I personally find it wasteful to be making insertions into a database into in increments. rather I think you have no choice but to do it all at once, because what if someone unselects a checkbox? you delete your entry? not only is it wasteful but it would be too cumbersome to keep track of the cursor positions or _id, you’d need to re-query every-time that something had been added. What you should do is maintain a list of what needs to be added to the database and bulk insert it when it’s done.
First you need to make an object holding the data that you want inserted. An object is the cleanest way cause you need to hold multiple pieces of information. Here it is very simple, you insert values you want into the constructor and then retrieve with the getter methods.
public class ContactObject {
private String name;
private String phone;
public ContactObject(String name, String phone) {
super();
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
}
Now you need an object to hold these objects when they’re checked. weird I know, but it much more convenient if they’re identified and can be iterated over and in general are batched together for referencing. I think this sort of task calls for the HashMap. Make it in the constructor.
contactMap = new HashMap<Integer, ContactObject>(c.getCount());
Now it’s time to boogie. make methods for the checkbox to add and remove stuff from your HashMap.
cbInsert.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (cbInsert.isChecked()) {
contactMap.put(cursor.getPosition(), new ContactObject(str_name, str_phone));
} else {
contactMap.remove(cursor.getPosition());
}
}
});
We’ve used the cursor position like an id for our objects within the HashMap. And when our checkbox is unchecked and we want to remove the object that was put in, we can just refer that that identifier that we used. Someone more prudent might want to check if something is there at the position before removing, that’s at your discretion. Now we’re almost done. How do we convert our HashMap to entries in the database? You have to access a database object, loop through and then get at your object one by one. The question is now is where. You could do it right in the adapter, but I usually do something like this in my activity because in my cases I usually have a database already made for the activity for other tasks and I don’t like to make more objects than I’m pushed to. So, what we can do is finish up with a getter method in our adapter for our HashMap:
public HashMap<Integer, ContactObject> getContactMap() {
return contactMap;
}
Now I’d imagine that you’d do something like this when your app is leaving so here goes.
#Override
protected void onDestroy() {
super.onDestroy();
HashMap<Integer, ContactObject> contactMap = adapter.getContactMap();
DatabaseHelper db = new DatabaseHelper(this);
// iterate through hashmap
for (Map.Entry<Integer, ContactObject> entry : contactMap.entrySet()) {
Integer key = entry.getKey();
ContactObject value = entry.getValue();
db.creatEntry(key, value.getPhone(), value.getName());
}
db.close();
}
Now things look a little weird, what happened what did i do with your entry method?
public long creatEntry(Integer id, String inputnumber, String name) { // for add data
long lng;
String strId = id.toString();
String[] selectionArgs = {strId};
Cursor cursor = ourdatabase.query(DATABASE_TABLE, null, "other_id = ?", selectionArgs, null, null, null);
if (cursor.moveToFirst()) {
// it exists, i'd assume that you might not want anything else done here
lng = -1;
} else {
// it doesn't exist
ContentValues cv= new ContentValues();
cv.put(KEY_NUMBER, inputnumber);
cv.put(N_NAME, name);
cv.put(KEY_OTHERID, strId);
Log.v(inputnumber, "adding to Database");
lng = ourdatabase.insert(DATABASE_TABLE, null, cv);
}
// cursor.close();
return lng;
}
As you can i see I modified it so that it takes in the id as well. I thought that you an issue that you'd run into would be having repeats in your database. I thought you could manage it by having another field for an id that you can modify.This id refers to the id passed in from the HashMap. I figured that every time that you make an insertion you first check if that previous id is there, then decide what you want to do. This is not a perfect solution but i just wanted to alert you that that issue is possible and give a possible hint as to manage it. In general the insert method should be fine if you only want to insert a couple rows but if you have a lot of stuff to insert, you might wanna look into bulk transactions for performance.
One more thing, checkboxes in your listview cannot be expected to have their states persist as you might normally expect it. You must explicitly dictate what state the checkbox has at each position. I made it correspond with if your HashMap has something filled with its corresponding key. Here's the full adapter method in hopes that it's made clearer:
public class ContactCursorAdapterCT extends CursorAdapter {
private LayoutInflater inflater;
private HashMap<Integer, ContactObject> contactMap;
public ContactCursorAdapterCT(Context context, Cursor c) {
super(context, c);
inflater = LayoutInflater.from(context);
contactMap = new HashMap<Integer, ContactObject>(c.getCount());
// i used c.getCount() as a capacity limit for this.
// if you opened made this multiple times, it might get over inflated and
// slow things down.
}
#Override
public void bindView(View view, Context context, final Cursor cursor) {
TextView name = (TextView)view.findViewById(R.id.contactlistTV1);
TextView phone = (TextView)view.findViewById(R.id.contactlistTV2);
final CheckBox cbInsert = (CheckBox) view.findViewById(R.id.contactlistCB1);
String str_name = cursor.getString
(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String str_phone = cursor.getString
(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
name.setText(str_name);
phone.setText(str_phone);
boolean isFilled = contactMap.containsKey(cursor.getPosition());
cbInsert.setChecked(isFilled);
// insert, remove objects to hashmap
cbInsert.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (cbInsert.isChecked()) {
contactMap.put(cursor.getPosition(), new ContactObject(str_name, str_phone));
} else {
contactMap.remove(cursor.getPosition());
}
}
});
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = inflater.inflate(R.layout.lvct, parent, false);
return v;
}
public HashMap<Integer, ContactObject> getContactMap() {
return contactMap;
}
}
It would be a good idea to add a flag to your records which will indicate that they are checked or not. Meaning an entry will have a name, a number and a flag (checked/unchecked).
In your Adapter itself, you can check the value of this flag an act accordingly.
(A related answer was regarding checkboxes in ListView is given here)

ContentProvider - returning multiple Cursors or custom Object

I have a working implementation of a ContentProvider loading data via CursorLoader into a listview (with custom CursorAdapter). It's a list of events. Every item has a title, place, etc. but also a set of offers which should be displayed in a LinearLayout inside every list row.
The problem is that a Cursor row can only contain flat data, not a set of other items.
My only idea is to make a joined query on database like this:
SELECT * FROM events, offers WHERE events.id=offers.event_id;
But then I'll have as much rows as there are offers (and the list should display events, so it's not good) and the list would be overpopulated. Maybe there is a possibility to tell CursorAdapter to only populate list rows with unique events.id but somehow retrieve the offers data as well?
The best solution would be to put a Cursor or custom Object containing offers inside the events Cursor. But afaik it's not possible.
I was facing the same problem. In fact, I think a lot of people are.
The whole mechanism of URI - to Relational DB through contentprovider, and everything that was built around it (like the various change listeners, file and stream handling) - this is all very impressive and useful, but for very simple data models.
Once your application needs a more elaborate data model, like - a hierarchy of tables, object relational semantics - this model breaks.
I've found a bunch of ORM tools for Android, but they seem too 'bleeding edge' to me (plus, for the life of me, I couldn't figure out if they have data change notification support).
ORM is very common today, I really hope the Android folks agree and add ORM capabilities to the platform.
This is what I ended up doing:
A cursor of cursors, with a a leading index cursor that helps choose the correct internal curosr.
It's kind of a temp solution, I just needed to move on with my code and get back to this later. Hope this helps.
Of course if you use a listview, you probably need to also create a custom adapter to inflate the correct views, and do the binding.
public class MultiCursor implements Cursor {
private final String TAG = this.getClass().getSimpleName();
ArrayList<Cursor> m_cursors = new ArrayList<Cursor>();
Map<Long, CursorRowPair> m_idToCursorRow = Collections.synchronizedMap(new HashMap<Long, CursorRowPair>());
Set<Long> m_idSet = new HashSet<Long>();
Cursor m_idCursor;
/**
* #precondition: _id column must exist on every type of cursor, and has to have index of 0 (be the first)
* #param idCursor
*/
public MultiCursor(Cursor idCursor) {
m_idCursor = idCursor;// this cursor binds the order (1,2,3) to ids
// go over all the ids in id cursor and add to m_idSet
initIdSet();
// m_cursors.add(idCursor);
// m_position = -1;
}
private void initIdSet() {
m_idSet.clear();
long id;
m_idCursor.moveToPosition(-1);
while (m_idCursor.moveToNext()) {
id = m_idCursor.getLong(m_idCursor.getColumnIndex(ContentDescriptor.ShowViewItem.Cols.ID));
m_idSet.add(id);
}
m_idCursor.moveToFirst();
}
public void addCursor(Cursor cursor) {
// when something changes in the child cursor, notify parent on change, to notify subscribers
// cursor.registerContentObserver(new SelfContentObserver(this)); // calls my onchange, which calls the ui
m_cursors.add(cursor);
updateIdToCursorMap(cursor);
}
private class CursorRowPair {
public final Cursor cursor;
public final int row;
public CursorRowPair(Cursor cursor, int row) {
this.cursor = cursor;
this.row = row;
}
}
private void updateIdToCursorMap(Cursor cursor) {
// get object_type
// for each row in cursor, take id, row number
// add id, <cursor,rowNum> to map
long id;
int row = 0;
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
id = cursor.getLong(cursor.getColumnIndex(ContentDescriptor.ShowViewItem.Cols.ID));
if (m_idSet.contains(id)) m_idToCursorRow.put(id, new CursorRowPair(cursor, row));
row++;
}
cursor.moveToFirst();
}
private Cursor getInternalCursor() {
if (getPosition() < 0 || getCount()==0) return m_idCursor; // todo throw a proper exception
// get the id of the current row
long id = m_idCursor.getLong(m_idCursor.getColumnIndex(ContentDescriptor.BaseCols.ID));
CursorRowPair cursorRowPair = m_idToCursorRow.get(id);
if (null == cursorRowPair) return null;
Cursor cursor = cursorRowPair.cursor;
int row = cursorRowPair.row;
cursor.moveToPosition(row);
return cursor;
}
// //////////////////////////////////////////////
#Override
public void close() {
Log.d(TAG, "close");
for (Cursor cursor : m_cursors) {
cursor.close();
}
m_idCursor.close();
}
#Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
Log.d(TAG, "copyStringToBuffer");
getInternalCursor().copyStringToBuffer(columnIndex, buffer);
}
etc etc etc.
In you're adapter query the offers cursor for all records and make it a class variable. Then in your getView use the event id to iterate through the offer cursor and add the necessary textviews to your row layout when it find an appropriate match. It's not elegant, but it should work.
Unfortunately one CursorLoader can only load one Cursor. So the solution was to write a custom AsyncTaskLoader which returned two Cursors.

Using buffer in sqlite select statement android

i'm making select statement and it works fine
the problem is selecting more than thousands of records ate one and this cause the program too slow.
is there a possibility to select fifty by fifty and when select the first fifty record show them then add the next fifty record to them.
how can i do that .
thanks in advance ...
Use LIMIT/OFFSET Clauses is selection statement
I haven't worked on that but can give some idea reagarding that. You can use AsynTask here. In the doingInbackground() you can get the records and then you can call publishProgress() when 50 records are fetched and update the UI.
UPDATE:
You can use the LIMIT/OFFSET clause that Kiran said to get the limit of the record fetched and can update the UI using AsyncTask.
Here is the code you need to back your AutoCompleteTextView with a cursor adapter.
#Override
protected void onResume() {
super.onResume();
text = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
final AdapterHelper h = new AdapterHelper(this);
Cursor c = h.getAllResults();
startManagingCursor(c);
String[] from = new String[] { "val" };
int[] to = new int[] { android.R.id.text1 };
CursorAdapter adapter = new MyCursorAdapter(this,
android.R.layout.simple_dropdown_item_1line, c,
from, to);
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
if (constraint == null) {
return h.getAllResults();
}
String s = '%' + constraint.toString() + '%';
return h.getAllResults(s);
}
});
text.setAdapter(adapter);
}
class MyCursorAdapter extends SimpleCursorAdapter {
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
}
public CharSequence convertToString(Cursor cursor) {
return cursor.getString(cursor.getColumnIndex("val"));
}
}
The database that I am using has 3k rows of data in it and the Autocomplete works fine.
The things to note are that you need make sure that the your adapter puts the correct value in the text box once a user selects it. Do this with the convertToString method (at the end of the snippet above). You get to this method by extending SimpleCursorAdapter and overriding the method as shown.
Then you need to provide a FilterQueryProvider to your adapter. This allows your query to be run with the where clause of your typed text. If you have a huge dataset, then setting the threshold large enough (either programatically, or in xml) will prevent the filter query running until it will return a suitably sized resultset.
Hope this is useful.
Anthony Nolan

Categories

Resources