-1

I am using a Custom List Adapter with ViewHolder pattern to inflate views into my List that shows an image (width = match_parent), some text on the left (below the image) and a button on the right(also below the image).

Here is the code for the adapter class -

public class DishItemListAdapter extends ArrayAdapter<DishItem> {

    //declare fonts - BOLD and LIGHT
    final Typeface tf_light = Typeface.createFromAsset(getContext().getAssets(),
            "fonts/Roboto-Thin.ttf");
    final Typeface tf_bold = Typeface.createFromAsset(getContext().getAssets(),
            "fonts/Roboto-Regular.ttf");

    //get item count
    CartItemCount cartItemCount;

    //count for dish at particular position
    ArrayList<Integer> dishCountList = new ArrayList<>();

    //for matching key string in SharedPrefs
    String existingKeyString;

    Typeface font_light, font_bold;
    /* List of DishItem Objects shown on the Dashboard */
    private List<DishItem> DishItemList = new ArrayList<DishItem>();

    public DishItemListAdapter(Context context, int textViewResourceId) {
        super(context, textViewResourceId);
        this.font_bold = tf_bold;
        this.font_light = tf_light;
    }

    /* Add a New DishItem Item (object) to the list of DishItems on the Dashboard i.e. DishItemList */
    @Override
    public void add(DishItem object) {
        DishItemList.add(object);
        super.add(object);
    }

    @Override
    public int getCount() {
        return this.DishItemList.size();
    }

    @Override
    public DishItem getItem(int index) {
        return this.DishItemList.get(index);
    }

    @Override
    public View getView(final int position, final View convertView, ViewGroup parent) {
        Log.e("getView() at " + position, "");
        View row = convertView;
        final DishItemViewHolder viewHolder;

        // A ViewHolder keeps references to children views to
        // avoid unnecessary (and expensive) calls to findViewById() on each row.
        if (row == null) {
            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflater.inflate(R.layout.list_item_dish, parent, false);

            //instantiate DishItem View Holder
            viewHolder = new DishItemViewHolder();

            //get BUTTON for Adding Dish to Cart
            viewHolder.addToCart = (Button) row.findViewById(R.id.add_to_cart_button);

            //BUTTONS for + and - (adding and removing items from cart)
            viewHolder.addItemButton = (Button) row.findViewById(R.id.increase_item_count);
            viewHolder.removeItemButton = (Button) row.findViewById(R.id.decrease_item_count);

            //DISH NAME, CHEF NAME, DISH PRICE and DISH IMAGE
            viewHolder.dishName = (TextView) row.findViewById(R.id.dish_name_textview);
            viewHolder.chefName = (TextView) row.findViewById(R.id.chef_name_textview);
            viewHolder.dishPrice = (TextView) row.findViewById(R.id.dish_price_textview);
            viewHolder.dishImage = (ImageView) row.findViewById(R.id.dish_imageview);

            //image absolute path
            viewHolder.imageStorePath = new String();

            //image for depicting whether image is VEG or NON VEG
            viewHolder.veg_nonveg_indicator = (ImageView) row.findViewById(R.id.veg_nonveg_indicator);

            //viewSwitcher for switching between BUTTON and - + button
            viewHolder.viewSwitcher = (ViewSwitcher) row.findViewById(R.id.viewswitcher);

            //indicator for item added to Cart
            viewHolder.addedToCartIndicator = (TextView) row.findViewById(R.id.added_to_cart_text_indicator);
            viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);

            //counter for number of items selected for a particular dish
            viewHolder.dishQuantity = (TextView) row.findViewById(R.id.dish_quantity);

            //set tag for the ViewHolder
            row.setTag(viewHolder);

        } else {
            /* Get the ViewHolder back to get fast access to the DishItem UI widgets (views) */
            viewHolder = (DishItemViewHolder) row.getTag();
        }

        //create object of Item Count Class
        cartItemCount = new CartItemCount();


        /* fetch DishItem View at current position (="position") */
        final DishItem dishItem = getItem(position);

        row.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("clicked"+position,"");
                viewHolder.dishName.setText("CLICKED");
            }
        });
        return row;
    }

    static class DishItemViewHolder {
        TextView dishName;
        TextView chefName;
        TextView dishPrice;
        TextView dishQuantity;

        String imageStorePath;

        boolean isDishItemSelected;

        Button addToCart, addItemButton, removeItemButton;

        ImageView veg_nonveg_indicator;

        ImageView dishImage;

        ViewSwitcher viewSwitcher;

        TextView addedToCartIndicator;

    }
}

PROBLEM

Suppose I add 6 DishItem beans (model) to the list. Then, when I perform onClick on the 1st item in the list, the 1st item's text changes to CLICKED as it should. Also, in the Log, in says clicked:0 (as 1st list item's index is 0).

But the text for 4th list item also changes to CLICKED, which it shouldn't.

Now I read this post explaining the recycling mechanism of ListView.

However, I DO NOT want it to work that way since I want to update only those items I click. What am I doing wrong here? Is there any workaround this recycling mechanism to update only the particular item that I click?

UPDATE

Problem solved. I followed BlackBelt's approach and have thus accepted his answer, but I'd like to thank all of you for your inputs !! :)

Here is the updated getView() method.

 /**

*

* @param position The position of the item within the adapter's data set of the item whose view we want.

* @param convertView The old view to reuse, if possible.

* Note: You should check that this view is non-null and of an appropriate type before using.

* If it is not possible to convert this view to display the correct data, this method

* can create a new view. Heterogeneous lists can specify their number of view types,

* so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).

* @param parent The parent that this view will eventually be attached to

* @return A View corresponding to the data at the specified position.

*/

    @Override

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

        View row = convertView;

        final DishItemViewHolder viewHolder;


        // A ViewHolder keeps references to children views to

        // avoid unnecessary (and expensive) calls to findViewById() on each row.

        if (row == null) {

            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            row = inflater.inflate(R.layout.list_item_dish, parent, false);


            //instantiate DishItem View Holder

            viewHolder = new DishItemViewHolder();


            //get BUTTON for Adding Dish to Cart

            //viewHolder.addToCart = (Button) row.findViewById(R.id.add_to_cart_button);

            viewHolder.addItemButton = (Button) row.findViewById(R.id.add_to_cart_secondary_button);

            viewHolder.removeItemButton = (Button) row.findViewById(R.id.remove_from_cart_button);


            //DISH NAME, CHEF NAME, DISH PRICE and DISH IMAGE

            viewHolder.dishName = (TextView) row.findViewById(R.id.dish_name_textview);

            viewHolder.chefName = (TextView) row.findViewById(R.id.chef_name_textview);

            viewHolder.dishPrice = (TextView) row.findViewById(R.id.dish_price_textview);

            viewHolder.dishImage = (ImageView) row.findViewById(R.id.dish_imageview);


            //image absolute path

            viewHolder.imageStorePath = new String();


            //image for depicting whether image is VEG or NON VEG

            viewHolder.veg_nonveg_indicator = (ImageView) row.findViewById(R.id.veg_nonveg_indicator);


            //indicator for item added to Cart

            viewHolder.addedToCartIndicator = (TextView) row.findViewById(R.id.added_to_cart_text_indicator);

            viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);


            //set tag for the ViewHolder

            row.setTag(viewHolder);


        } else {

            /* Get the ViewHolder back to get fast access to the DishItem UI widgets (views) */

            viewHolder = (DishItemViewHolder) row.getTag();

        }


        //get object for CART ITEM COUNT class

        final CartItemCount cartItemCount = new CartItemCount();


        //get current dish item (MODEL from Bean class)

        final DishItem dishItem = getItem(position);


        //disable any highlighting unless dish is selected (verified from SharedPreferences)

        viewHolder.dishImage.setColorFilter(null);


        //hide ITEM COUNT indicator over the image unless dish is selected (again, verified from SharedPreferences)

        viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);


        //show the + and - buttons on the right and left (respectively) side on the Dish ImageView

        viewHolder.addItemButton.setVisibility(View.VISIBLE);

        viewHolder.removeItemButton.setVisibility(View.VISIBLE);


        //get data from Preferences (to see which dish is selected)

        SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);

        Map<String, ?> allEntries = pref.getAll();

        for (Map.Entry<String, ?> entry : allEntries.entrySet()) {

            Log.d("KEY = " + entry.getKey(), " VALUE = " + entry.getValue().toString());

        }


        //get Count for each dish in the list and set Quantity in the Model (DishItem.java)

        if (pref != null) {

            int currentDishCount = pref.getInt("dishCount" + position, 0);

            Log.e("Current DishCount", String.valueOf(currentDishCount));

            if (currentDishCount > 0) {

                getItem(position).setisDishItemSelected(true);

                dishItem.setDishQuantity(currentDishCount);

                Log.d("dish item" + position," selected");

            }

        }


        //update Views for selected DishItems

        if (dishItem.isDishItemSelected()) {

            viewHolder.dishImage.setColorFilter(Color.parseColor("#80E0E0E0"));

            viewHolder.addedToCartIndicator.setVisibility(View.VISIBLE);

            viewHolder.addedToCartIndicator.setText(dishItem.getDishQuantity() + " items in cart");

        }


        viewHolder.addItemButton.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                dishItem.setisDishItemSelected(true);

                dishItem.setDishQuantity(dishItem.getDishQuantity() + 1);


                //save data to preferences

                SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);

                SharedPreferences.Editor editor = pref.edit();

                editor.putInt("dishCount" + position, dishItem.getDishQuantity());

                editor.commit();


                //increment Total Number of Items in Cart

                int itemCount = cartItemCount.getitemCount();

                cartItemCount.setitemCount(itemCount + 1);

                Log.d("itemCount =", String.valueOf(itemCount));

                //broadcast the value of itemCount to MainMenuActivity

                Intent intent = new Intent("NEW_CART_ITEM");

                intent.putExtra("value", cartItemCount.getitemCount());

                getContext().sendBroadcast(intent);

                Log.d("broadcast", "sent");


                //notify adapter of change in underlying data (i.e. update View to show Changes in Model)

                notifyDataSetChanged();



            }

        });



        viewHolder.removeItemButton.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Log.d("Old Dish Qty. ", String.valueOf(dishItem.getDishQuantity()));

                //if dishCount has reached ZERO, set Dish as NOT SELECTED for buying

                if (dishItem.getDishQuantity() == 0) {

                    dishItem.setisDishItemSelected(false);


                } else {

                    dishItem.setisDishItemSelected(true);

                    dishItem.setDishQuantity(dishItem.getDishQuantity() - 1);

                    Log.d("New Dish Qty.", String.valueOf(dishItem.getDishQuantity()));


                    //decrement TOTAL number of items in Cart

                    int itemCount = cartItemCount.getitemCount();

                    cartItemCount.setitemCount(itemCount - 1);

                    Log.d("itemCount =", String.valueOf(itemCount));

                    //broadcast the value of itemCount to MainMenuActivity

                    Intent intent = new Intent("NEW_CART_ITEM");

                    intent.putExtra("value", cartItemCount.getitemCount());

                    getContext().sendBroadcast(intent);

                    Log.d("broadcast", "sent");


                    //recheck -> if dish Count has reached ZERO, set Dish as NOT SELECTED for buying

                    if (dishItem.getDishQuantity() == 0) {

                        dishItem.setisDishItemSelected(false);

                    }

                }


                //save Current Quantity of Dish Selected to SharedPreferences

                SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);

                SharedPreferences.Editor editor = pref.edit();

                editor.putInt("dishCount" + position, dishItem.getDishQuantity());

                editor.commit();


                //notify adapter of change in underlying data (i.e. update View to show Changes in Model)

                notifyDataSetChanged();

            }

        });


        return row;

    }

The idea was simple (I didn't know it before) -

  • In the onClick Listener for the buttons, change the Model i.e. Data i.e. DishItem using the setters and getters defined in the bean class.
  • call

    notifyDataSetChanged()

to tell the adapter about changes in data so that Views can be adjusted accordingly.

  • In the getView() method, set views bases on those data values.
Community
  • 1
  • 1
Kunal Chawla
  • 1,236
  • 2
  • 11
  • 24
  • yous there is a workaround but workarounds should not be used, so even if you find it out please dont use it – pskink Sep 09 '15 at 12:26

3 Answers3

2

Is there any workaround this recycling mechanism to update only the particular item that I click?

No workaround here. You have to change the dataset (at position - the item you clicked), and ask the Adapter to redraw the ListView's children. I would strongly suggest you to use the OnItemClickListener too.

Blackbelt
  • 156,034
  • 29
  • 297
  • 305
  • Could you please elaborate on this ? I mean, how do I change the dataset ? And by "redraw the ListView's children" you mean I should call notifyDataSetChanged from within the adapter class ? – Kunal Chawla Sep 09 '15 at 12:26
  • use the `OnItemClickListener` to retrieve the clicked item, and use the obtained reference to change its properties, then call notifyDataSetChanged – Blackbelt Sep 09 '15 at 12:28
  • Your solution works perfectly for itemClickListener ! But suppose I don't want to implement any action when the user clicks the whole list item (say a CardView), but only the small button in the list item (`viewHolder.addToCart`). What then? – Kunal Chawla Sep 09 '15 at 16:24
  • Tag the item at position on the button (see view.setTag/getTag). When onclick is fired use getTag to retrieve the item change its properties and call notifyDataSetChanged – Blackbelt Sep 09 '15 at 16:28
  • I am sorry I am a total noob at this. I'm stuck at one BIG issue right now. _How_ do I call (in ListFragment (not my adapter class)) `onClickListener` for a button at some `position` in the ListView, without calling `onItemClickListener()` ? – Kunal Chawla Sep 09 '15 at 17:49
  • because when I implement onClickListener for the button(s) in my adapter, the views get recycled. – Kunal Chawla Sep 09 '15 at 17:59
  • If you call setTag inside getView, you shouldn't have any problem. If you have problems update the question. I'll have a look – Blackbelt Sep 09 '15 at 18:13
  • I think I should stop messing around with the code _trying to get it to work_ and instead spend a whole day studying ViewHolder and ListViews. Right now I'm totally lost and that means my fundamentals concepts about ListView and ViewHolder are wrong. Thanks for your kind help! I'll surely update the question tomorrow. – Kunal Chawla Sep 09 '15 at 18:24
1

To change something, you must know the position of your item:

mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // Code here
            }
        });

this will give you the correct position you've touch it. Other way, inside your getView (in the Adapter):

DishItemList = dishCountList.get(position);

That way you'll have the position of your item. To touch only the one you want:

row.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View arg0) {
                  // here you can DishItemList.get("and get the key you need");
                }
    });
Mariano Zorrilla
  • 7,165
  • 2
  • 34
  • 52
  • But suppose I don't want to implement any action when the user clicks the whole item, but only the small button in the list item (viewHolder.addToCart). Is it possible to access viewholder items outside my custom Adapter ? – Kunal Chawla Sep 09 '15 at 12:52
  • Outside the Adapter you can only hit the all row. You can change "row.setOnClickListener" for "mButton.setOnClickListener" and do it with a button (or the name you put it) but you must use DishItemList = dishCountList.get(position); to know the real position of that button. – Mariano Zorrilla Sep 09 '15 at 12:56
0

You should reset the view's state to the default one in the getView and apply the item's data after this. It works like this:

  1. Get the row's view (either the convertView or inflate a new one)
  2. Create and attach a view holder
  3. Get the current item's data and state
  4. Apply ALL the state params and data (so write "Clicked" if the current item is clicked or clear the TextView's text otherwise - do not assume that it contains the correct text already)

You'd need to store the item's state somewhere - currently you use the TextView's text to do this and it breaks due to the recycling behaviour. The correct way is to add a field to the DishItem or just create a new class to contain the state of each item (selected, focused and so on - all the states you'd like to support). In the onClickListener change the state's value and then change the views' contents or just call notifyDataSetChanged on the adapter.

Samuil Yanovski
  • 2,837
  • 17
  • 19