1

I'm creating a custom class in which I associate a Cursor (populated with values extracted from a SQLite database) to an AutoCompleteTextView, with an ArrayAdapter. Each record extracted from Cursor is represented by an ID and a value, and it's added to an ArrayAdapter by value. While adding values, I also create two ArrayList to keep track of both IDs and values.

I'd like to be able to get selected item position, but I actually cannot do it, even with onItemClick Listener.

Here it is some code from my custom class:

private AutoCompleteTextView field;
private String column;
private Activity activity;
private ArrayList<String> list_id, list_values;

//Constructor
public PopulateAutoComplete(int elementFromLayout, String column, Activity activity) {
    this.field = (AutoCompleteTextView) activity.findViewById(elementFromLayout);
    this.activity = activity;
    this.column = column;
}
//Reset two lists associated to actual element
private void initializeLists() {
    list_id= new ArrayList<>();
    list_values= new ArrayList<>();
}
//Populating methods
public void populate(Cursor cursor_total, String column_id, String column_values) {
    initializeLists();
    int i=0;
    String id = null;
    String value = null;
    cursor_total.moveToFirst();
    do {
        id = cursor_total.getString(cursor_total.getColumnIndex(column_id));
        value = cursor_total.getString(cursor_total.getColumnIndex(column_values));
        list_id.add(i,id);
        list_values.add(i,value);
        i++;
    } while (cursor_total.moveToNext());
    adapter = new ArrayAdapter<>(activity,android.R.layout.simple_dropdown_item_1line, list_values);
    field.setAdapter(adapter);
}
//Method which select the right item from the list
public void selectValue(Cursor cursor_single, String column_value_to_select) {
    String id_to_verify = cursor_single.getString(cursor_single.getColumnIndex(column_value_to_select);
    loop: {
        for (int i=0; i<listaID.size(); i++) {
            if (list_id.get(i).equals(id_to_verify)) {
                adapter.getItem(i);
                field.setText(list_values.get(i));
                break loop;
            }
        }
    }
    setListener();
}
private void setListener() {
    field.setOnItemClickListener(listener);
}
private AdapterView.OnItemClickListener listener = new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        //How to get ID of selected item here?
    }
};

And here it's the code from my MainActivity:

PopulateAutoComplete element = new PopulateAutoComplete(R.id.element, "column", this);
element.populate(cursor,"id","name");
element.selectValue(cursor_single,"id");

I'd like to have the ID of selected item, so I can use list_id.getItem(position).

I tried with field.getListSelection(), list_id.indexOf(adapterView.getSelectedItem()) but it was not helpful. I also know that some of this method are related to actual dropdown list, but I need a method which extract the exact position of an item in the ArrayAdapter; in this way, I can automatically extract ID and values (note: values are not unique).

EDIT #1:

public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        adapterView.getSelectedItemPosition();   //It returns "-1"
        i;  //It returns a result depending on actual shown dropdown list. If I have a list of 200 item and I have 1 item shown on the dropdown, position will be always 0
    }
};
Flash
  • 1,134
  • 10
  • 12
  • Cursor and ArrayAdapter? why? why dont you use SimpleCursorAdapter? see [here](http://stackoverflow.com/a/19860624/2252830) for details – pskink Oct 02 '15 at 08:39
  • list_id.indexOf(adapterView.getSelectedItem()) won't work here, because list_id is a list of IDs (strings) while getSelectedItem() returns an object representing the data of the currently selected item in the list (probably the value, because that's what you're forwarding to your adapter in its constructor). What you want is perhaps getSelectedItemPosition(), but there's a more immediate solution. See my answer below. – logcat Oct 02 '15 at 08:57
  • @pskink: I'm going to try SimpleCursorAdapter because I suppose it's the best way to manage cursor data; by the way, is it suitable to use with AutoCompleteTextView? – Flash Oct 02 '15 at 09:04
  • @logcat: about your solution, when I use the position of _onItemClick_ method, it shows position related on dropdown list, not on the full one.. And getSelectedItemPosition() returns me _-1_ – Flash Oct 02 '15 at 09:05
  • it is because i use `MatrixCursor` and in `runQuery` i do something like this: `c.newRow().add(i)` here `i` counts form 0, 1, 2 ..., if you return your database `Cursor` with `_id` column everything will work as you want: you will get right `id` parameter in `onItemClick(AdapterView> parent, View view, int position, long id)` method, the last parameter of this method: no need for `getSelectedItemPosition` at all ;-) – pskink Oct 02 '15 at 09:11
  • and of course: don't use any `ArrayAdapter` let alone `BaseAdapter` which is the worst solution in your case... – pskink Oct 02 '15 at 09:16
  • Ok, I never used MatrixCursor before, so I will have to study it and to try a little before having some results, I will need to have the same results I have with Cursor also with MatrixCursor, because that _long id_ from *onItemClick()* method is actually necessary to my app development. Are you sure that I cannot reach the result with normal Cursor actually, only replacing ArrayAdapter with SimpleCursorAdapter? – Flash Oct 02 '15 at 09:31
  • no you dont need Matrixcursor at all, use your database cursor, see: http://codeshare.io/N5SMV, it uses `ContentResolver#query` and filters out the phone's contacts since i didnt want to create a real sqlite db, but you most likely use `db.rawQuery` or similar call – pskink Oct 02 '15 at 09:46
  • I have an application with onItemClick() in multiple classes with different lists. These lists have many more items in their database than what's displayed to the screen. I print the value of the position argument and it prints the exact position in my data structure (like 31 in a list containing 32 items when I scroll to the bottom and tap the last one). Never ran into a problem with the value of position. If you have 100 items in list_id, you're forwarding 100 items to your adapter, or do you filter the list? – logcat Oct 02 '15 at 09:48
  • I've just tried @pskink solution: during autocomplete, the component doesn't filter elements (for example, if i type "F", it shows the whole alphabet). By the way, inside **onItemClick()**, I don't get 'id' as the the result from the query, to be clear and position is the 'real' position on the dropdown, not filtered – Flash Oct 02 '15 at 10:29
  • @logcat: I actually forwarding all the items from my DB to my adapter, do you suggest to filter them? How could I do that? Even in this case, I only need to refer to the ID of the database record associated to selected item into the adapter and, to be honest, I don't know yet how to reach the scope. I select the last item on your list of 32 items (position=31), that element contains "id", "name": name is the text value shown by the **AutoCompleteTextView** element, id is an integer which I need to extract – Flash Oct 02 '15 at 10:32
  • did you run my code with contacts? what doesnt work there? – pskink Oct 02 '15 at 10:34
  • I just edited my answer. Please try it. BTW, other types of adapters definitely have their uses, but because adapters are messy and error prone as your logic and UI grow, I think it's a good idea to stick with the most basic type of adapter (BaseAdapter), unless you're implementing something really basic. It was advised by others to override getView() when not using BaseAdapter as the UI gets complex or more functionality is needed, which is already most of the work in BaseAdapter... – logcat Oct 02 '15 at 10:37
  • Sorry, I was thinking about a simple list, not about the drop down list of an auto complete. My bad. Sorry. – logcat Oct 02 '15 at 10:48
  • 1
    ok i changed my code to work with real db: see http://codeshare.io/N5SMV again, it has two columns: `CustomerId` and `Name` in table `C`, change it to match your db – pskink Oct 02 '15 at 10:51
  • I changed my answer. Someone else asked a similar question. However, it won't work if you have two identical values in your list_value (such as "item4" appearing twice), but it doesn't make sense to have the same value appear more than once in the search list. – logcat Oct 02 '15 at 11:09
  • @pskink: Yes, I did, but there were two problems. Firstable, if I start typing any words, it shows the whole list, instead of a list filtered by the words I type. The second problem is that ID got from *onItemClick* wasn't correct, I'll try to check again. As soon as possible, I will try your new code, thank you – Flash Oct 02 '15 at 19:06
  • just change column nanes/table name, note how rawQuery is called, you can also call DatabaseUtils#dumpCursor before returning the cursor – pskink Oct 02 '15 at 19:09
  • @logcat: Ok, I will try using *BaseAdapter* too and I will let know about results here. Unluckily the application will be given to people, so it's possible to have two identical value (for example, two men named "John Lennon" with different IDs) and I need to differentiate them, even if you cannot see any differences on the list. This is very important – Flash Oct 02 '15 at 19:10
  • and please do not use any BaseAdapter, it is waste of time really – pskink Oct 02 '15 at 19:11
  • @pskink: Yes, I noted it. But this way there will be several queries instead of one query during loading of my Activity, am I wrong? By the way, if this returns me the correct ID, I will use it – Flash Oct 02 '15 at 19:12
  • call dumpCursor before returning the cursor it will help you understand how it works, also you will verify if ids are correct – pskink Oct 02 '15 at 19:13
  • @pskink: Thanks, your solution is working! Are you sure that using *FilterQueryProvider* is a good solution? It runs several queries to Database, each time I modify AutoCompleteTextView's text. Other note, I need to to a Cast operation in order to get "id" value from my adapter (I'll wrote down on my first post): *Cursor c = ((Cursor)cursorAdapter.getItem(position));* then I can extract "id" column as wanted.. Is there another way to extract this kind value? EDIT: if you put your answer, I will vote it for sure, thanks again – Flash Oct 05 '15 at 10:36
  • see the last parameter of `onItemClick` and yes it runs several queries to the sqlite db, after all sql db is for getting the data in a optimised way, isn't it? – pskink Oct 05 '15 at 11:10
  • You're right. About last parameter (id), it shows value of column *_id*, I need value of column 'id' instead, which is different because it's not related to the query, but to my records on DB. That's why I was asking if there is a better solution instead of Casting like I did 2 posts ago. Sorry if I wasn't able to explain my issue – Flash Oct 05 '15 at 14:23
  • 1
    ok, you have two columns: `"id"` and `"name"`, right? you are making a query something like: `"select id as _id, name from...."`, right? so the last parameter of `onItemClick` is the `id` of pressed item, which is `_id` column which is in turn the alias for your `id` db column, just call `DatabaseUtils.dumpCursor` in `runQuery` method and you will see what values you are getting from your database, if this is not the `id` you are looking for so i have no idea what `id` do you want to get – pskink Oct 05 '15 at 15:56
  • see again the link i shared: http://codeshare.io/N5SMV and the end i dumped the content of my db, it has 4 records with CustomerId: 11, 22, 33 and 44, if you type `"a"` and click on `"Jack Fonda"` you will get `id` == 33 in `onItemClick` – pskink Oct 05 '15 at 16:05
  • Ok, I will check it as soon as possible and I'll update here, thanks for your support and patience meanwhile. By the way, I have *"SELECT rowid AS _id, id, name FROM table"*, but as you suggested, I could simply replace it with *"SELECT id as _id, name FROM table"*, this was my only problem, I didn't think about it because I added *_id* column because it was necessary for AutoCompleteTextView. I'll have to do some replacements in my code, but I will solve everything this way! – Flash Oct 05 '15 at 18:16
  • yes, `SELECT id as _id, name FROM table` will be fine, with this you will get `id` as the last parameter of `onItemClick`, now you are getting `rowid`, let me know how it went – pskink Oct 05 '15 at 19:26
  • It's working! Using the alias **_id** on the query executed at *runQuery()* solved last problems! I'll post full solution asap. Thanks a lot – Flash Oct 06 '15 at 08:35

2 Answers2

0

This is my final solution. Thanks pskink for your support.

Methods for AutoCompleteTextView custom class:

//1) Insert all the elements into the AutoCompleteTextView
public void populate(String tag, String column_shown) {
    this.TAG = tag;  //I use a tag in order to differentiate queries I need to execute
    this.column_value =column_shown;
    String[] from = {column_shown};
    int[] to = {android.R.id.text1};
    cursorAdapter = new SimpleCursorAdapter(activity, android.R.layout.simple_dropdown_item_1line, null, from ,to, 0);
    setSearchFilter();
    cursorAdapter.setStringConversionColumn(1);
    field.setAdapter(cursorAdapter);
}
//2) Specify which query to use during the auto-complete step (while typing)
private void setSearchFilter() {
    FilterQueryProvider provider = new FilterQueryProvider() {
        @Override
        public Cursor runQuery(CharSequence constraint) {
            System.out.println("PAC - runQuery: " + constraint);
            if (TextUtils.isEmpty(constraint)) {
                return null;
            }
            String[] params = {"%" + constraint.toString() + "%"};
            //Different queries for different tags
            switch (TAG) {
                case ("report"): {
                    Cursor c = db.getReport(column_value, params);
                    //db is an istance of a Custom Class for SQLiteDatabase
                    return c;
                }
            }
            return null;
        }
    };
    cursorAdapter.setFilterQueryProvider(provider);
}
//3) Method to select the correct value while loading Activity for the first time. The value is taken from DB
public void selectValue(Cursor c) {
    if (c==null) {
        return;
    }
    if (c.getCount()==0) {
        return;
    }
    String id_to_verify = c.getString(c.getColumnIndex(column_id));
    setID(id_to_verify);  //I need this to take memory of actual record ID
    field.setText(c.getString(c.getColumnIndex(column_value)));  //field is an instance of AutoCompleteTextView custom class
}

Methods for SQLiteDatabase custom class. This was the important part for my issue: I need to specify an _id column inside my query to get methods working.

public Cursor getReport(String column, String params[]) {
    String query = "SELECT id AS _id, name FROM customers WHERE " + column + " LIKE ? ORDER BY name ASC;";
    return db.rawQuery(query,params);
}

Methods for MainActivity:

private PopulateAutoComplete customer;
@Override
protected void onCreate(Bundle savedInstanceState) {
    //...
    Cursor cursorCustomer = `...`;  //Used to find actual value on Activity loading
    customer = new PopulateAutoComplete(R.id.customer,"id_customer",db,this);
    customer.populate("report", "name");
    customer.selectValue(cursorCustomer);
}
Flash
  • 1,134
  • 10
  • 12
-1

Try the solution here. I assume the same value can't appear twice in the data list (for example, you can't have "item1" twice in list_values:

how to find the position of item in a AutoCompletetextview filled with Array

Community
  • 1
  • 1
logcat
  • 283
  • 2
  • 12
  • By the way, why are you using arraylist.add(i, id) and arraylist.add(i, value)? I see you increment i by 1 in each iteration and the items are maintained in insertion order, so you can simply use add(id) and add(value). – logcat Oct 02 '15 at 08:41
  • About _list_id.get(i) inside onItemClick()_, I tried it, but it shows result depending on dropdown, not depending on the whole list. So, if I search for a particular item, it always returns _i = 0_ because it's the first item on my actual dropdown list, sadly. That's the core of my problem: having onItemClick position based on actual dropdown and not on the whole list. About indexing, you are right, I'm going to delete that iteration, thanks (I think I added it only for further security) – Flash Oct 02 '15 at 08:45