New to Android, I got a simple application with spinners and associated ArrayAdapters working: when things get selected, I seem to be able and trigger some calculations. I am then saving the current selected item.
At some point, I retrieve the saved value, and want to position the spinner at that value: basically setPosition() the spinner to that object.
I have found lots of tutorials with the same format I have: use the getPosition() on the ArrayAdapter, and pass in the object you are looking for... Trouble is, it keeps returning -1 (not found).
Debugging, I have verified that the object I pass is not null, and so is the ArrayAdapter, and also the ArrayAdapter getCount returns me the items it should have (so it's not empty).
I'm at loss. Appreciate any... pointers? :-)
/* ArrayAdapter class looks like this*/
public class MyAdapter extends ArrayAdapter<MyClass> {
// Constructor
MyAdapter(#NonNull Context context, int resource, #NonNull List<MyClass> objects) {
super(context, resource, objects);
}
}
/* Fragment looks like this*/
final MyAdapter mAdapter = new MyAdapter(
requireActivity(),
android.R.layout.simple_spinner_item,
objects);
Spinner mySpinner = fragment_view.findViewById(R.id.my_spinner);
mySpinner.setAdapter(mAdapter);
// assume I have one "object" of MyClass,
// and want to search for it in "MyAdapter"
int spinnerPosition = mAdapter.getPosition(objectToBeFound); // returns -1
mySpinner.setSelection(spinnerPosition);
Adapter internally works with List
public int getPosition(#Nullable T item) {
return mObjects.indexOf(item);
}
so getPosition internally depends upon List#indexOf(T) and which relies on equals method
(o==null ? get(i)==null : o.equals(get(i)))
so you are getting -1 because you haven't implemented equals and hashcode method properly in MyClass so implement both methods and you will be able to get the precise index.
Referene:
Use Auto Generate
difference between equals() and hashCode()
Related
I am using listview in my app.I am adding items to list with this line:
conversationsAdapter.add(user);
and this initializes list
conversationsAdapter=new ArrayAdapter<JsonObject>(this,0) {
#Override
public View getView(int c_position,View c_convertView,ViewGroup c_parent) {
if (c_convertView == null) {
c_convertView=getLayoutInflater().inflate(R.layout.random_bars,null);
}
JsonObject user=getItem(c_position);
String name=user.get("name").getAsString();
String image_url="http://domain.com/photos/profile/thumb/"+user.get("photo").getAsString();
TextView nameView=(TextView)c_convertView.findViewById(R.id.tweet);
nameView.setText(name);
ImageView imageView=(ImageView)c_convertView.findViewById(R.id.image);
Ion.with(imageView)
.placeholder(R.drawable.twitter)
.load(image_url);
return c_convertView;
}
};
ListView conversationsListView = (ListView)findViewById(R.id.conversationList);
conversationsListView.setAdapter(conversationsAdapter);
conversationsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
startChat(conversationsAdapter.getItem(position));
}
});
My list view is looking like this:
I want to update an item in the list.How can I do this ?
Example:We can write a method like: changeName when this method calls,method sets name "Tolgay Toklar" to "Tolgay Toklar Test" so I want to update custom listview item attributes.
I totally disagree with tyczj. You never want to externally modify an ArrayAdapter's list and yes it's possible to update just an individual item. Lets start with updating an individual item.
You can just invoke getItem() and directly modify the object and call notifyDataSetChanged(). Example:
JSONObject object = conversationAdapter.getItem(position);
object.put("name", data);
conversationAdapter.notifyDataSetChanged();
Why does this work? Because the adapter will feed you the same object reference used internally, allowing you to modify it and update the adapter. No problem. Of course, I'd recommend instead building your own custom adapter to perform this directly on the adapter's internal list. As an alternative, I highly recommend using the ArrayBaseAdapter instead. It already provides that ability for you while fixing some other major bugs with Android's ArrayAdapter.
So why is tyczj wrong about modifying the external list? Simple. There's no guarantee that your external list is the same as the adapters. Once you perform a filter on the ArrayAdapter, your external list and the adapters are no longer the same. You can get into a dangerous scenario where (for example) index 5 no longer represents position 5 in the adapter because you later added an item to the adapter. I suggest reading Problems with ArrayAdapter's Constructors for a little more insight.
Update: How External List Fails
Lets say you create a List of objects to pass into an ArrayAdapter. Eg:
List<Data> mList = new ArrayList<Data>();
//...Load list with data
ArrayAdapter<Data> adapter = new ArrayAdapter<Data>(context, resource, mList);
mListView.setAdapter(adapter);
So far so good. You have your external list, you have an adapter instantiated with it and assigned to listview. Now lets say at some later point, the adapter is filtered and cleared.
adapter.filter("test");
//...later cleared
adapter.filter("");
Now at this point mList is NOT the same as the adapter. So if the adapter is modified:
adapter.add(newDataObject);
You'll find that mList does not contain that new data object. Hence why external lists like this can be dangerous as the filter creates a NEW ArrayList instance. It won't continue to use your mList referenced one. You could even try adding items to mList at this point and it won't be reflected in the adapter.
If you change the data in your list you need to call notifyDatasetCanged on the adapter to notify the list that the underlying data has changed needs to be updated and.
Example
List<MyData> data = new ArrayList<MyData>();
private void changeUserName(String name){
//find the one you need to change from the list here
.
.
.
data.set(myUpdatedData);
notifyDatasetChanged()
}
I have a class ShoppingList with the following Map:
#DatabaseField(dataType = DataType.SERIALIZABLE)
Map<Category, List<Product>> products;
Then, on a Fragment, I have a ListView that should show all Categories. Therefore, I set the Adapter for it with the following code:
aisles.setAdapter(new ShoppingListEditAdapter(getActivity(), new ArrayList<Category>(mShoppingList.getMap().keySet())));
By default it should be empty. If I click a button, a Dialog opens, and there I input the text for the new category or aisle that I want to create. Thus, I run the following code:
Category category = new Category();
category.setName(editText.getText().toString());
mShoppingList.getMap().put(category, new ArrayList<Product>());
try {
helper.getShoppingListDao().update(mShoppingList);
((BaseAdapter) aisles.getAdapter()).notifyDataSetChanged();
} catch (SQLException e) {
e.printStackTrace();
}
This should modify the ShoppingList I have for this Fragment, adding a Category to the Map. However, even if I call notifyDatasetChanged() for my Adapter or restart the application, the ListView doesn't get populated with the data. Both Category and Product are Serializable. Why doesn't it get populated?
In keeping with what I've posted as a comment, here is a simple adapter that will accept a Map and display the keys of the map as the values of the list. I've not tested it in accordance with the actual item id's, but for displaying a map that will be mutated, it seems to work (i.e. if the map itself is changed and notifyDataSetChanged() is called, it will work). I've counted on ArrayAdapter to do the heavy lifting and just overrode the methods I wanted.
The obvious downside is I'm not sure how expensive it is to call keySet().toArray() but I imagine it's a linear time operation, beyond the extra memory usage. But basically I don't know a great way to get around this issue, as it relies on taking a Set into some sort of ordered collection.
public class MapAdapter<K, V> extends ArrayAdapter<K> {
private Map<K, V> items;
public MapAdapter(Context context, int resource, Map<K,V> items) {
super(context, resource);
this.items = items;
}
#Override
public int getCount() {
return items.size();
}
#Override
public K getItem(int position) {
return ((K []) items.keySet().toArray())[position];
}
}
When you create a custom adapter extending ArrayAdapter<T>, it has usually the form:
public class ListAdapter extends ArrayAdapter<Item> {
private List<Item> mData;
public ListAdapter(Context context, int resource, List<Item> data) {
super(context, resource, data);
mData = data;
}
}
The data is initially saved in a private member mData, but also the ArrayAdapter saves the data in its own member mObjects. I am pretty sure those are not actual copies, but references to the same list.
Now, and this is my question, if during the ListView processing, for some reason, you have to replace your own list with a fresh new List, I think you should also do:
mData = new List<Item>();
super.clear();
super.addAll(mData);
otherwise there will be no consistency in ListView, and methods like getFilter().filter() will not work.
Am I correct?
I think, when you say mData = data; it only copies pointer of the data array, because when you execute that;
ListAdapter adapter = new ListAdapter(context, resource, data);
data.clear();
adapter.notifyDataSetChanged();
it changes list. So it keeps pointer of your source array,
Second, I think (not sure) you cannot use filter function of adapter, at least I couldn't use and write my own filter function. I filter elements from sqlite(I take my elements from database). and use notifyDataSetChanged function of adapter.
You are right. Your ListAdapter doesn't make a deep copy of the provided list of Items. This means that changing an Item instance 'outside' the ListAdapter will put the ListAdapter in an invalid state.
However, you can 'fix' this by calling notifyDataSetChanged on the ListAdapter.
List<Item> itemList = ....
....
....
ListAdapter adapter = new ListAdapter(this, R.layout.somelayout, itemList);
....
Now, if you change an item 'outside' the ListAdapter, you can still make your ListAdapter be in sync with the change:
itemList.get(idx).changeSomethingInItem("Hello"); // Changes the Item at index 'idx'.
adapter.notifyDataSetChanged(); // Notify adapter about this change.
you really needn't pretty sure whether it actual copies or not ,just extend BaseAdapter
I have just implemented an autocompletion for a textfield using an online webservice, based on this answer on Stackoverflow:
ArrayAdapter is updated late from Webservice in AutoCompleteTextAdapter
Using an ArrayAdapter<User> implements Filterable, I have managed that the autocompletion suggests me entries as intended.
User is a Java Bean that contains information which is presented in the suggestion (age, name, ...).
When I select a suggestion, the Autocomplete field is filled with the 'wrong' data - using the toString()method, instead of the 'name' property of the bean.
My Question is: Can I override (in the Adapter) a method which will allow me to specify how to convert the bean so that the correct property is returned for the AutoCompleteTextView?
(Ideally, User.toString() should not be changed)
Thx!
There is no need subclassing AutoCompleteTextViewand override the convertSelectionToStringmethod. The same thing is achievable by overriding the convertResultToStringmethod in your custom Filter in (your already subclassed) ArrayAdapter.
I had the same problem: custom objects in my ArrayAdapter whose toString() implementation wasn't something I could control. I implemented the method like this:
// In custom Filter implementation
#Override
public CharSequence convertResultToString(Object result) {
if(result instanceof MyCustomClass) {
return ((MyCustomClass) result).getAttribute("name");
}
return super.convertResultToString(result);
}
The search results depend on what the data's toString() returns. In your case you need to return name field in the toString() implementation.
I have found another way:
The method convertSelectionToString(Object selectedItem) in AutoCompleteTextView can be overridden by subclasses to allow for custom conversions. This way, no adjustment to the toString() method is required.
This - it seems to me - has the advantage that the Filter can return not just a list of Strings but a list of custom objects, which can be used by getView(int position, View convertView, ViewGroup parent) of the adapter to construct "richer" suggestions.
The obvious disadvantage is that it requires subclassing AutoCompleteTextView for every Filterresult type whose toString() method shall not be modified.
#Override
protected CharSequence convertSelectionToString(Object selectedItem) {
if(selectedItem instanceof User){
User u = (User) selectedItem;
return u.getUsername();
} else {
return super.convertSelectionToString(selectedItem);
}
}
Any comments on this?
If you subclass your own adapter from SimpleCursorAdapter, you can set a CursorToStringConverter on the Adapter in the constructor.
private class AutoCompleteAdapter extends SimpleCursorAdapter {
public AutoCompleteAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
/* Other setup code here */
setCursorToStringConverter(new CursorToStringConverter() {
#Override
public CharSequence convertToString(Cursor item) {
return item.getString(item.getColumnIndex(DESIRED_COLUMN_NAME));
}
});
}
}
I have a custom spinner (customized for formatting). It works fine and shows the result of the selected array item. The string array with my data is called mydata[].
I want to do something with that result - I've tried hours of changes but, it seems I don't know what the container is for the selected result - it just displays automatically. The mNumber refers to a case select in a class (it's result is based on what's passed into-it).
My question(s) - refer to the * WHAT DO I USE HERE * shown in the code's last line:
1. What is the container for the result?
2. How to access and syntax it?
Here's the code:
Thank you!
**[declared in onCreate]**
Spinner spinner = (Spinner) findViewById(R.id.Spinner01);
MySpinnerAdapter adapter = new MySpinnerAdapter(this, R.layout.mspinner, R.id.text,mydata);
spinner.setAdapter(adapter);
**[declared outside of onCreate]**
#SuppressWarnings("unchecked")
private class MySpinnerAdapter extends ArrayAdapter{
public MySpinnerAdapter(Context context, int resource, int textViewResourceId, String[] objects) {
super(context, resource, textViewResourceId, objects);
final TextView woof =(TextView)findViewById(R.id.TextView07);
woof.setText(String.valueOf(dogday.mNumber(*** WHAT DO I USE HERE ? ***)));
}
}
EDITED - FOR MY RESPONSE TO COMMONSWARE (too many characters for a comment box).
Thanks. You know the result that gets displayed in a spinner (by default) when something's selected - that's the piece of data I need. I want to use it as an argument for a call.
The user selects something in the spinner - The selected item will be used to make some choices in the class method (dogDay), which takes a data argument for mNumber(data) and returns a result (just like a function).
I want to do some math calcs with the result. First, I want to display what's coming back (for now) so, I'm using the dogDay.mNumber(data) as an argument for woof.text.
My question is this, How to get the piece of data (the thing the user selected in the Spinner)? How did the spinner know what to display for my selection - that's what I want? I tried using something like getSelectedItem (or whatever the it was - I can't remember just now) but, it crashes.
Is their an easier way to custom format a spinner? (and get the data) I searched hi and low for info and found only one applicable to android 1.5/later (I want a spinner with all black background and red text - I can do it via the way shown in my code using a the custom layout).
Thanks - I got a bit long winded!
EDITED - with full code
Here's a full code with the custom spinner and the call you suggested. As I mentioned, I already tried it - it only shows the first item in the list - never the selected one. I use the result of getSelectedItem as an argument for the string... I tried it from both within and outside the adapter...
package com.bt.junk;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
public class MyMaincode extends Activity {
private static String mydata[] = {"one", "two", "three"};
int poop;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// DECLARATIONS ------------------------------------------------
Spinner spinner = (Spinner) findViewById(R.id.Spinner01);
MySpinnerAdapter adapter = new MySpinnerAdapter(
this,R.layout.custom_spinner_row,R.id.text, mydata );
spinner.setAdapter( adapter );
poop = spinner.getSelectedItemPosition();
}//end onCreate ********************************************
// METHODS, CLASSES, etc ---------------------------------------
#SuppressWarnings("unchecked") //<-- I added that
private class MySpinnerAdapter extends ArrayAdapter{
public MySpinnerAdapter(Context context, int resource,
int textViewResourceId, String[] objects) {
super(context, resource, textViewResourceId, objects);
final TextView sayWhat = (TextView) findViewById(
R.id.TextView01);
sayWhat.setText(String.valueOf(mydata[poop]));
}//end MySpinnerAdapter
}//end class MySpinnerAdapter
}//end activity
Your question makes little sense to me. I am assuming that "I want to do something with that result" means "I want to find out the selected item's position in the array". If that assumption is correct, you can get the selected position for a Spinner by calling getSelectedItemPosition(). This will be 0 when the Spinner first appears, unless you change the position yourself.
Your code is also very strange, IMHO. The constructor of an ArrayAdapter should not be attempting to manipulate a widget.