listView.setSelection(n); m=listView.getSelectedItemPosition(); //m != n. why? - android

calling ListView's setSelection() seems to have a problem. a lot of people ask about it. there are answers but none is working. ignoring issues of visual affects. here's a basic scenario that results in unexpected results:
listView.setSelection(5); //listView is a ListView. there are >= 6 items in the list
int sel=listView.getSelectedItemPosition();
you would expect sel==5 but actually it's -1 (which method didn't work?)
so is this a bug and if not, what are the rules that govern the setting and retrieval of the selected item?

If you see in the documentation of setSelection you find this:
Sets the currently selected item. If
in touch mode, the item will not be
selected but it will still be
positioned appropriately. If the
specified selection position is less
than 0, then the item at position 0
will be selected.
That way, it makes perfectly sense that it returns -1. The item is not selected even if you run this method when you're in touch mode, as you most likely are.

I agree with Eric's answer. However, if you still want to make it work, here is a work around.
for your onItemClick part use the following (I had multiple listviews...)
public void onItemClick(AdapterView<?> parentView, View v, int chosenPosition, long
myLong) {
switch(parentView.getId()){
case R.id.Hrlist:
parentView.setSelection(chosenPosition);
break;
case R.id.Minlist:
parentView.setSelection(chosenPosition);
break;
case R.id.Seclist:
parentView.setSelection(chosenPosition);
}
}
Then in your other method you can use something along the lines of:
HrList.getFirstVisiblePosition();
Assuming HrList is defined as
ListView HrList = (ListView) findViewById(R.id.Hrlist);
So yes, in touch mode it doesn't register the item as selected. However, it does move it to a dependable location (i.e. at the top) which you can use to get the value.

Related

How can I prevent a race condition in Android RecyclerView's Adapter?

I have a classic implementation of a recycler view that, when I click on an item inside the recycler view, that item gets deleted.
The problem is that, when I successively click twice one after another (without any noticeable delay between the clicks) on an item in that recycler view, then the second click on that same item is registered at a different position.
The way I identify the item that received the click is by holder.adapterPosition (where holder is an instantiation of ViewHolder class). I wonder if I'm doing wrong by relying on this.
To further troubleshoot, I added the following println statement to troubleshoot:
println("layoutpos ${holder.layoutPosition} adapterpos ${holder.adapterPosition} oldpos ${holder.oldPosition}")
Then, as I repeated those successive clicks, I got the following output in Android Studio's Run tab:
[Galaxy_Nexus_API_22 [emulator-5554]]: I/System.out: layoutpos 1 adapterpos 1 oldpos -1
[Galaxy_Nexus_API_22 [emulator-5554]]: I/System.out: layoutpos 0 adapterpos -1 oldpos -1
Right now, my thoughts are: use adapterPosition, and ignore it when its value is -1 (assume that -1 means a declaration of a racing condition). But I feel that I might be missing something deeper.
How should I handle this situation?
Show the user that the system is refreshing while you're disabling the user from deleting a new object until the previous transaction is completed.
I found two solutions:
if (holder.adapterPosition == -1) return // Race condition; do nothing
// else, do stuff
This does the trick. However, it is not elegant in my view, as: why receive clicking events to begin with if we are not supposed to? It doesn't seem to be solving the problem from its roots.
To solve it more elegantly (from its roots), I did this in the setOnClickListener:
holder.item.setOnClickListener {
// we don't want 2nd click right? so let's delete the listener
holder.item.setOnClickListener{}
/* then, do the stuff for this listener. this stuff
will be done once, as we deleted its listener earlier,
so, subsequent clicks are not possible. */
}
This way, the item with that listener is clicked on once, and a second click does not happen to begin with, hence a racing condition is not possible from its roots. Because the clicking listener is deleted right when the first click is received. Should I want to allow the item to get clicks again, I can redefine a listener for it again.

listview.pointToPosition providing INVALID value when should not

I am getting information from my application in production, but when I am trying to reproduce the problem I am not able to do it. What I am looking for is any idea (as I am blocked) of how can I try to reproduce the error.
Basically I am having an activities with a listview, toolbar, edittext and admob advertisement.
My listview is composed of a relativealayout containing a textview and one imageview.
With the adapter I am attaching to the textview an OnLongClickListener that starts a drag operation.
To the Listview itself, I am adding an OnDragListener.
#TargetApi(11)
public class myDragEventListener implements View.OnDragListener {
// This is the method that the system calls when it dispatches a drag event to the
// listener.
public boolean onDrag(View v, DragEvent event) {
// Defines a variable to store the action type for the incoming event
final int action = event.getAction();
// Handles each of the expected events
switch(action) {
case DragEvent.ACTION_DRAG_STARTED:
return true;
case DragEvent.ACTION_DROP:
//We detect which is the item where the drop happened on.
int itemPosition = listShop.pointToPosition((int)event.getX(), (int)event.getY());
// An unknown action type was received.
default:
return true;
}
}
}
So, basically we initiate the drag on the textview from the listview item and the drop ends on the listview itself.
This code works properly, but I received from production reports that sometimes the value of itemPosition to be -1. To avoid an exception I can add a simple check, but I am worried about the user experience.
So, what I would like to avoid is a bad user experience with the application not responding properly, and we know is happening due to reports. The problem is that we are not able to reproduce.
Trying to reproduce this error we tried:
Longclick on textview and drop on imageview. (provides correct itemPosition)
Longclick on textivew and drop outside the listview (drag listener not called)
Longclick on textivew and drop at the edge of listview (works ok).
Does somebody has a suggestion of how this "-1" could be reproduced? Theoretically it should not happen.... is the drop (whatever the drag that has been initiated) activates the OnDragListener, that means that the position where the drop occurs is a position of the listview. How can the DragListener be called in a listview in an incorrect position?
Any idea what could be happening?
Ok, I found the problem. My listview covers almost all the screen, and sometimes is feed with items in the list that are not covering the complete list up to the end. When an item is dragged and dropped in the space where there is no item in the list, provide that value.
The problem is that my testing mobile is very small compared to other devices, and that mobile was always having the screen fully covered by items of the list, so any drag/drop operation was always captured.

Hard/Fast fling using the swipe to dismiss library and a ContentProvider shows the wrong item

I have been developing an app that has a listview in which items may be removed by the user.
Now I have been trying to use the swipe to dismiss library in my project, by Roman Nurik: https://github.com/romannurik/Android-SwipeToDismiss.
I've successfully implemented a button which removes a row (click), however I am unable to use the swipe to dismiss functionality of the library (swipe). This is all because of the canDismiss check I need to do.
I have a cursor (see ContentProvider and LoaderCallbacks) which I was trying to iterate over and use to determine if a row can be dismissed. However this does not seem to work, 'cause when I fling fast/hard it displays the second to last row twice (it's normal position and the last position) and it does the same thing for the first and second rows.
Has anyone ever done something similar? And how did you solve this issue?
My code:
Cursor cursor = (Cursor) mAdapter.getItem(position);
if (cursor.getInt(Card.CARD_REMOVABLE_COLUMN_INDEX) == 1) {
return true;
}
But everytime I try to use the cursor, the issue seems to appear.
Alright, I guess there is no good solution, other than fetching the required information from the view(s) itself. Unlike with web development, getting the information from the database all the time, is not an option.
I guess you could get the information from the viewtag if you would so desire. I've done something different, using the position I get from the library, I lookup the row in the listview, and check if certain views are visible (every row can be removable, hence they all have the same header(s), I check if a header is visible or not, and handle this).

Alternative to ListView smoothScrollToPosition()?

I have a ListView that potentially can have hundreds of entries. When a selection is made I've been using a smoothScrollToPosition, thusly:
if (lv != null) { //Are we created yet?
lv.post(new Runnable() {
public void run() {
lv.smoothScrollToPosition(k);
}
});
}
but my users have told me they don't like the scrolling animation and would prefer to just instantly go there. So I replaced my smooth scroll with
lv.setSelection(k);
... and now it does nothing at all. FWIW this is all happening right after a notifyDatasetChanged
In searching for a solution I came across this discussion on http://code.google.com/p/android/issues/detail?id=6741 which implies this is a known problem. Is there a workaround or am I just doing this wrong?
Thanks in advance.
The documentation of setSelection says that it only scrolls to the selected position when the ListView is in touch mode. Maybe ListView is no more in touch mode once the data set has changed or maybe setSelection is simply forgotten for the next UI update cycle.
I guess you could try a workaround by calling setSelection with a delay. You could use the postDelayed method with a delay of 100 milliseconds for example. Or you could extend ListView and override layoutChildren or something related that probably gets called when the data set changes in order to re-calculate the list view item measurements. At that point it should be safe to call setSelection and you don't need to rely on guesstimating a delay.

How to use Android Spinner like a drop-down list

It's taken me quite a while to get my head around the Android Spinner. After several failed implementation attempts, and after reading many questions partially similar to my own but without satisfactory answers, and some without any answers at all, e.g. here and here, I finally get that a "spinner" in Android isn't meant to be the same thing as a "drop-down list" from desktop apps, or a select in HTML. However, what my app (and I'm guessing the apps of all the other posters whose questions are similar) needs is something that works like a drop-down box, not like a spinner.
My two problems are with what I first considered to be idiosynchrasies the OnItemSelectedListener (I've seen these as separate questions on this site but not as one):
An initial selection of the first list item is triggered automatically without the user's interaction.
When the item that was already selected is selected again by the user, it is ignored.
Now I realise that, when you think about it, it makes sense for this to happen on a spinner - it has to start with a default value selected, and you spin it only to change that value, not to "re-select" a value - the documentation actually says: "This callback is invoked only when the newly selected position is different from the previously selected position". And I've seen answers suggesting that you set up a flag to ignore the first automatic selection - I guess I could live with that if there's no other way.
But since what I really want is a drop-down list which behaves as a drop-down list should (and as users can and should expect), what I need is something like a Spinner that behaves like a drop-down, like a combo-box. I don't care about any automatic pre-selection (that should happen without triggering my listener), and I want to know about every selection, even if it's the same one as previously (after all, the user selected the same item again).
So... is there something in Android that can do that, or some workaround to make a Spinner behave like a drop-down list? If there is a question like this one on this site that I haven't found, and which has a satisfactory answer, please let me know (in which case I sincerely apologise for repeating the question).
+1 to David's answer. However, here's an implementation suggestion that does not involve copy-pasting code from the source (which, by the way, looks exactly the same as David posted in 2.3 as well):
#Override
void setSelectionInt(int position, boolean animate) {
mOldSelectedPosition = INVALID_POSITION;
super.setSelectionInt(position, animate);
}
This way you'll trick the parent method into thinking it's a new position every time.
Alternatively, you could try setting the position to invalid when the spinner is clicked and setting it back in onNothingSelected. This is not as nice, because the user will not see what item is selected while the dialog is up.
Ok, I think I've come up with a solution for my own situation with the help of both David's and Felix' answer (I believe David's helped Felix', which in turn helped mine). I thought I'd post it here together with a code sample in case someone else finds this approach useful as well. It also solves both of my problems (both the unwanted automatic selection and the desired re-selection trigger).
What I've done is added a "please select" dummy item as the first item in my list (initially just to get around the automatic selection problem so that I could ignore when it was selected without user interaction), and then, when another item is selected and I've handled the selection, I simply reset the spinner to the dummy item (which gets ignored). Come to think of it, I should've thought of this long ago before deciding to post my question on this site, but things are always more obvious in hindsight... and I found that writing my question actually helped me to think about what I wanted to achieve.
Obviously, if having a dummy item doesn't fit your situation, this might not be the ideal solution for you, but since what I wanted was to trigger an action when the user selected a value (and having the value remain selected is not required in my specific case), this works just fine. I'll try to add a simplified code example (may not compile as is, I've ripped out a few bits from my working code and renamed things before pasting, but hopefully you'll get the idea) below.
First, the list activity (in my case) containing the spinner, let's call it MyListActivity:
public class MyListActivity extends ListActivity {
private Spinner mySpinner;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: other code as required...
mySpinner = (Spinner) findViewById(R.id.mySpinner);
mySpinner.setAdapter(new MySpinnerAdapter(this));
mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> aParentView,
View aView, int aPosition, long anId) {
if (aPosition == 0) {
Log.d(getClass().getName(), "Ignoring selection of dummy list item...");
} else {
Log.d(getClass().getName(), "Handling selection of actual list item...");
// TODO: insert code to handle selection
resetSelection();
}
}
#Override
public void onNothingSelected(AdapterView<?> anAdapterView) {
// do nothing
}
});
}
/**
* Reset the filter spinner selection to 0 - which is ignored in
* onItemSelected() - so that a subsequent selection of another item is
* triggered, regardless of whether it's the same item that was selected
* previously.
*/
protected void resetSelection() {
Log.d(getClass().getName(), "Resetting selection to 0 (i.e. 'please select' item).");
mySpinner.setSelection(0);
}
}
And the spinner adapter code could look something like this (could in fact be an inner class in the above list activity if you prefer):
public class MySpinnerAdapter extends BaseAdapter implements SpinnerAdapter {
private List<MyListItem> items; // replace MyListItem with your model object type
private Context context;
public MySpinnerAdapter(Context aContext) {
context = aContext;
items = new ArrayList<MyListItem>();
items.add(null); // add first dummy item - selection of this will be ignored
// TODO: add other items;
}
#Override
public int getCount() {
return items.size();
}
#Override
public Object getItem(int aPosition) {
return items.get(aPosition);
}
#Override
public long getItemId(int aPosition) {
return aPosition;
}
#Override
public View getView(int aPosition, View aView, ViewGroup aParent) {
TextView text = new TextView(context);
if (aPosition == 0) {
text.setText("-- Please select --"); // text for first dummy item
} else {
text.setText(items.get(aPosition).toString());
// or use whatever model attribute you'd like displayed instead of toString()
}
return text;
}
}
I guess (haven't tried this) the same effect could be achieved using setSelected(false) instead of setSelection(0), but re-setting to "please select" suits my purposes fine. And, "look, Ma, no flag!" (Although I guess ignoring 0 selections is not that dissimilar.)
Hopefully, this can help someone else out there with a similar use case. :-) For other use cases, Felix' answer may be more suitable (thanks Felix!).
Look. I don't know if this will help you, but since you seem tired of looking for an answer without much success, this idea may help you, who knows...
The Spinner class is derived from AbsSpinner. Inside this, there is this method:
void setSelectionInt(int position, boolean animate) {
if (position != mOldSelectedPosition) {
mBlockLayoutRequests = true;
int delta = position - mSelectedPosition;
setNextSelectedPositionInt(position);
layout(delta, animate);
mBlockLayoutRequests = false;
}
}
This is AFAIK taken from 1.5 source. Perhaps you could check that source, see how Spinner/AbsSpinner works, and maybe extend that class just enough to catch the proper method and not check if position != mOldSelectedPosition.
I mean... that's a huge "maybe" with a lot of "ifs" (android versioning comes to mind etc.), but since you seem frustrated (and I've been there with Android many times), maybe this can give you some "light". And I assume that there are no other obvious answers by looking at your previous research.
I wish you good luck!
Here is an alternative solution to differentiate between any (intended or unintended) programmatic and user-initiated changes:
Create your listener for the spinner as both an OnTouchListener and OnItemSelectedListener
public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
boolean userSelect = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
userSelect = true;
return false;
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (userSelect) {
// Your selection handling code here
userSelect = false;
}
}
}
Add the listener to the spinner registering for both event types
SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
This wouldn't handle the case in which the re-selection of the same item by the user doesn't trigger the onItemSelected method (which I have not observed), but I guess that could be handled by adding some code to the onTouch method.
Anyway, the problems Amos pointed out were driving me crazy before thinking of this solution, so I thought I'd share as widely as possible. There are many threads that discuss this, but I've only seen one other solution so far that is similar to this: https://stackoverflow.com/a/25070696/4556980.
Modifying the Spinner is useful if you want to have multiple selections simultaneously in the same activity.
If you only desire the user to have a hierarchical selection, for example:
What do you want to eat?
Fruit
Apples
Bananas
Oranges
Fast Food
Burgers
Fries
Hot dogs,
then the ExpandableListView might be better for you. It allows the user to navigate a hierarchy of different groups and choose a child element. This would be similar to having several Spinners for the user to choose from - if you do not desire a simultaneous selection, that is.
I worked through several of the issues mentioned in this thread before I realized that the PopupMenu widget is what I really wanted. That was easy to implement without the hacks and workarounds needed to change the functionality of a Spinner. PopupMenu was relatively new when this thread was started in 2011, but I hope this helps someone searching for similar functionality now.

Categories

Resources