2

I had this weird scenario where every time I select a value from the spinner in the ListView's 1st Item, the last ListView'sitem its spinner value is the same as the first item. This will only happen when the total number of ListView items is 5 and above. I commented out the codes and retain just the declarations and it's still happening. Is this a bug in Android?

Clarifications:

  • My ListView's Scroll Listener is empty

  • My spinner's setOnItemSelectedListener is commented out.

  • Android SDK Tool version is 22.6.2

  • Android SDK Platform-Tools is 19.0.1

    enter image description here

Here's the adapter code:

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    Viewholder v = new Viewholder();
    v.rowView = convertView;
    LayoutInflater inflater = (LayoutInflater) context
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    if (v.rowView == null) {
        v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent, false);
        v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
        v.rowView.setTag(v);
    } else {
        v = (Viewholder) v.rowView.getTag();
    }

    return v.rowView;
}

ViewHolder:

class Viewholder{
    View rowView;
    TextView itemPID;
    TextView itemPrice;
    TextView itemItemId;
    Spinner spinner;
    TextView subtotal;
}

XML:

<Spinner
    android:id="@+id/spinner1"
    android:layout_width="100dip"
    android:layout_height="wrap_content"
    android:layout_margin="20dip"
    android:entries="@array/quanitiy"
    android:layout_alignTop="@+id/itemPrice"
    android:layout_toRightOf="@+id/imageDisplay" />

Array:

   <string-array name="quanitiy">
        <item>Quantity</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
        <item>6</item>
        <item>7</item>
        <item>8</item>
        <item>9</item>
        <item>10</item>
    </string-array>

UPDATE I commented out the code for OnItemClickListner. I updated the code above, still the problem exists. The only thing left is the declarations.

Scenario: If I select 1 at the first item of the spinner [index 0] of the ListView, the last item of ListView's spinner gets also 1 without interaction. When down the listview's items until the last part, that's where I found out that they are both the same. I commented out the codes and just retained the declarations.

Community
  • 1
  • 1
rahstame
  • 2,148
  • 4
  • 23
  • 53
  • Please indent your code. Then I'll look at it ;) If you're using Eclipse, Ctrl+Maj+F is your friend! – Joffrey Apr 02 '14 at 11:12
  • There is no `return` statement in your `getView()`, it shouldn't compile at all. – Joffrey Apr 02 '14 at 11:17
  • I added a return statement. – rahstame Apr 02 '14 at 11:21
  • Can you post the XML for `R.layout.inner_base_header_cutom` – Joffrey Apr 02 '14 at 11:27
  • Ok thanks, actually I was looking for what's in the spinner so if you could post the entries too, that'd be great – Joffrey Apr 02 '14 at 11:32
  • Also, I don't get why you use a `Viewholder` here, seems to add much overhead for no particular use, and also makes things harder to read, while they are very simple. – Joffrey Apr 02 '14 at 11:34
  • Have you tried to run this code? Does it even compile? There is a `v.` missing, or you're using a field with same name as `ViewHolder`'s `itemPrice` field, not shown here, which is either way confusing. Please post code that compiles, and please explain your problem. I don't understand how you see 2 entries selected in the spinner. – Joffrey Apr 02 '14 at 11:38
  • v is the `ViewHolder`, yes the code is working. The only problem is when I select only the first item the last item also will be affected. – rahstame Apr 02 '14 at 11:42
  • Affected in what way? Are you displaying something? Maybe a screenshot of your UI (with explanations about what is not as expected) would help people help you. – Joffrey Apr 02 '14 at 15:27
  • @Joffrey : I updated the codes and descrptions. I hope you can better understand my issue. – rahstame Apr 03 '14 at 08:14
  • AdapterView goes like this: every time the getView is called you have to set every View's data corresponding to it's position. So you should save the current item for every spinner and set it in every getView call before returning. – Yaroslav Mytkalyk Apr 03 '14 at 08:53
  • It won't change anything, but please don't recreate an inflater every time. – Stephane Mathis Apr 03 '14 at 09:30
  • how can help me? http://stackoverflow.com/questions/38550046/how-insert-spinner-into-listview – user6612196 Jul 24 '16 at 09:04

1 Answers1

4

Ok now I got it. Thanks for the clarifications. Your problem comes from views being recycled. You should take a look at this post to understand recycling better.

The short answer

Basically, when you use the convertView in getView(), you are reusing the view of an item that is no longer visible. That's why its fields (including the spinner, the textview for the price...) are already set to something, and you should set them yourself in getView(). The values to give these textfields and the spinner should depend on the item at the specified position.

The detailed answer

Here's what recycling does (picture taken from the post previously mentioned):

recycling process

Your problem

Here when you scroll to display a new item (let's say item 8, like the picture), the view used to display it is the same as the one for the first item (item 1). Therefore, when you select something on the spinner of the first item, and then scroll down, you will see the change in the last item too because your getView doesn't change the values of the views that were used for item 1 (but it is supposed to update them!).

Note: The correspondance of the first and the last item in your case is totally by chance. Usually, the view is reused for 2 items that are approximately separated by the number of items in one screen height, as you can see in the picture.

Inline explanation in YOUR code for getView

Viewholder v = new Viewholder(); // why create a new one, while you might reuse the former one?

// here rowView becomes the same as the former item's view, with all the former values
v.rowView = convertView; 

// you don't need this here but only in the case rowView is null, should be in your 'if'
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

if (v.rowView == null) {
    v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent,false);
    v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
    v.rowView.setTag(v);
    // here you inflated a new view (nothing reused) 
    // but you didn't initialize anything
} else {
    v = (Viewholder) v.rowView.getTag();
    // here you're reusing the former View and ViewHolder 
    // and you don't change the values
}

The solution

Your not supposed to let the items behave as they please, but you're supposed to tell them what to display when you initialize the item view's content in getView(). For the picture above, this means you need to change what used to be item 1 into item 8.

You should keep the state of each item in your adapter. You probably have a backing list or something to display different elements in your ListView depending on the position, right? Then you should use a similar list (or the same) to store the value selected in the spinner, the value displayed by the text field for the price etc. These should probably correspond to fields of the items in your current list anyway, so you probably already have some place to store them.

On the other hand, when you reuse the convertView in getView(), ensure you initialize the spinner (and text field, and everything in this item view) with the correct values for the item at position.

Solution code (template) for getView

Viewholder v;

if (convertView == null) {
    // create a new holder for the new item view
    v = new Viewholder();
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent,false);
    v.rowView.setTag(v);

    // populate the new holder's fields
    v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
    v.itemPID = (TextView) v.rowView.findViewById(...); // put the right ID here
    v.itemPrice = (TextView) v.rowView.findViewById(...); // put the right ID here
    v.itemItemId = (TextView) v.rowView.findViewById(...); // put the right ID here
    v.subtotal =  (TextView) v.rowView.findViewById(...); // put the right ID here
} else {
    v = (Viewholder) convertView.getTag();
    // the views of v are already populated here (reused)     
}

/* 
 * Reused or not, the item view needs to be initialized here.
 * Initialize all views contained in v with values that are 
 * meaningful for the item at 'position'.
 */

// for instance:
MyItemClass theItemAtPosition = myBackingList.get(position);
v.subtotal.setText(String.valueOf(theItemAtPosition.getSubtotal()));
v.spinner.setSelection(theItemAtPosition.getQuantity());

// to be continued, you get the idea ;)
Community
  • 1
  • 1
Joffrey
  • 32,348
  • 6
  • 68
  • 100
  • Thank you for the explanation, it would require time for me to study your answer. Can you suggest a better code from this? Thanks again. – rahstame Apr 03 '14 at 08:43
  • 1
    I edited my answer to show you some code for your solution. But I can't do much more not knowing how your list looks like (I mean the list content, the backing list for the ListView) – Joffrey Apr 03 '14 at 08:53
  • 1
    Now it looks better, you should be able to understand thoroughly your problem ;) – Joffrey Apr 03 '14 at 09:42
  • I am not sure how to get the spinner position because It is not included on the items. Please see this: http://pastebin.com/a7Xd4UHT some comments are part of the running code. – rahstame Apr 03 '14 at 10:40
  • I have not read the whole code, but you should probably add a key in your map describing an item, for instance `KEY_QUANTITY` and set it with whatever is selected in the spinner (by default, maybe put 0). – Joffrey Apr 03 '14 at 11:27
  • Also, why don't create a class for your cart items instead of using `HashMap`s? It would make more sense IMHO. That way your adapter would take an `ArrayList` instead of `ArrayList>` – Joffrey Apr 03 '14 at 11:31
  • 2nd option: if adding a `quantity` property for your cart items doesn't suit your needs, you may also create an `ArrayList quantities;` in your adapter, initialize it with 0, and update it in your `onItemSelected` spinners method. You will then be able to initialize your spinners with the values contained in this list. **Be careful** with this approach, you'll need to take care of the updates if cart items are added/removed from the initial list. – Joffrey Apr 03 '14 at 11:37