0

There is a ListView which displays the items of an Arraylist, which are dynamically added by the user (via an EditText and a confirm button). In each item of the Listview, there is a remove ImageView, which removes the respective item from the List (and hence from the ListView).

The goal is to make the remove button (ImageView) on the ListView work correctly.

Currently I am adding an onClickListener to each of the items, when they are added to the List. The onClickListener executes a method which uses the list.remove(int i) function and then notifyDataSetChanged() to update the ListView.

[[MAIN ACTIVITY]]

public class MainActivity extends AppCompatActivity {

    // setting up member variables
    ArrayList<String> list = new ArrayList<>();
    ArrayAdapter<String> arrayAdapter;
    TextInputLayout textInputLayout;
    ListView listView;
    Button addNewListItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initialize();

    }

    private void initialize() {
        // binding listView, arrayadapter & button to the ones from the layout, setting up the array adapter

        // Listview & adapted
        listView = findViewById(R.id.listView_Items);
        arrayAdapter = new ArrayAdapter<>(this, R.layout.listview_item, R.id.listView_item_text, list);
        listView.setAdapter(arrayAdapter);

        // buttons
        addNewListItem = findViewById(R.id.button_addItem);

        // setting up the onclick listener for addItem, de/increasePeople, calculate
        addNewListItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addItem();
            }
        });

        // setting up the onclick listener for the ok button in the soft keyboard for the
        // edit text field when adding new items
        textInputLayout = findViewById(R.id.TextInputLayout_itemToAdd);
        textInputLayout.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_DONE) {
                    addItem();
                    return true;
                }
                return false;
            }
        });
    }

    private void addItem() {
        textInputLayout = findViewById(R.id.TextInputLayout_itemToAdd);
        String inputItem = textInputLayout.getEditText().getText().toString().trim();

        // checking, if text was entered in the edit text and then adding it to the list
        if (inputItem.isEmpty()) {
            textInputLayout.setError("Can't be empty");
        } else {
            textInputLayout.setError(null);
            list.add(inputItem);

            // Adding an onClickListener to an item of the listview requires the listView to
            // finish redrawing. OnLayoutChangeListener fires as soon as the listview has finished
            // redrawing the listview.
            // The layoutchangelistener is immediately removed and the onclicklistener is added.

            listView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    listView.removeOnLayoutChangeListener(this);

                    // getting the child count of the listview to get the index of the item, that
                    // just has been added
                    int addedItemId = listView.getChildCount();
                    Toast.makeText(MainActivity.this,"addedItemid: " + addedItemId,Toast.LENGTH_SHORT).show();
                    View addedItemChildView = listView.getChildAt(addedItemId-1);

                    // initializing the imageview to which the onclicklistener has to be added
                    ImageView removeImage = addedItemChildView.findViewById(R.id.ImageView_removeItem);

                    // onclicklistener is an anonymous class and therefore requires
                    // the index to be final. initializing a final int with the index
                    final int itemId = addedItemId-1;
                    removeImage.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Log.d("Logging","itemId: " + itemId);
                            removeListItem(itemId);
                        }
                    });
                }
            });
            arrayAdapter.notifyDataSetChanged();
        }
    }

    private void removeListItem(int listItemId) {
        list.remove(listItemId);
        arrayAdapter.notifyDataSetChanged();
    }
}

[[ACTIVITY_MAIN]]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/linearLayout_mainLayout"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <android.support.design.widget.TextInputLayout
        android:id="@+id/TextInputLayout_itemToAdd"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:errorEnabled="true">

        <android.support.design.widget.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/hint_enterItem"
            android:inputType="text" />
    </android.support.design.widget.TextInputLayout>

    <Button
        android:id="@+id/button_addItem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_addItem" />

    <ListView
        android:id="@+id/listView_Items"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />


</LinearLayout>

[[LISTVIEW_ITEM]]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:layout_marginTop="8dp"
    android:layout_marginBottom="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginLeft="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginRight="8dp">


    <ImageView
        android:id="@+id/ImageView_removeItem"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:clickable="true"

        app:srcCompat="@android:drawable/ic_menu_delete" />

    <TextView
        android:id="@+id/listView_item_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_weight="2"
        android:gravity="center_vertical"
        tools:text="test" />

    <android.support.design.widget.TextInputLayout
        android:id="@+id/listView_item_edit"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_weight="3"
        android:gravity="center"
        app:errorEnabled="false">

        <android.support.design.widget.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Enter Value"
            android:inputType="numberDecimal" />
    </android.support.design.widget.TextInputLayout>



</LinearLayout>

As you can see, when the onClickListener is being added to the ImageView the itemid is added in accordance with the current amount of items in the list (minus 1). If we have 3 items: "i0", "i1" and "i2" and "i1" will be removed, the onClickListener with the remove method on "i2" has the old index. "i2"'s index has been automatically decreased in the ArrayList but the remove-function parameter remains the same.

How can this problem be solved?

Thank you :)

(i hope i didnt remove any essential code as i tried to leave only the important parts)

Hooni
  • 354
  • 1
  • 4
  • 13

1 Answers1

2

The views inside a ListView item can be access by override getView() of the standard adapter. Try this [Use setTag() and getTag()]:

    arrayAdapter = new ArrayAdapter(this, R.layout.listview_item, R.id.listView_item_text, list){
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View itemView = super.getView(position, convertView, parent);
            ImageView removeImage = itemView.findViewById(R.id.ImageView_removeItem);

            removeImage.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = (int)v.getTag();
                    Log.d("Logging","itemId: " + pos);
                    remove(pos);
                    notifyDataSetChanged();
                }
            });

            removeImage.setTag(position);
            return itemView;
        }
    };

Hope that helps!

Another answer [Use final]:

    arrayAdapter = new ArrayAdapter(this, R.layout.listview_item, R.id.listView_item_text, list){
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View itemView = super.getView(position, convertView, parent);
        ImageView removeImage = itemView.findViewById(R.id.ImageView_removeItem);

        removeImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //int pos = (int)v.getTag();
                Log.d("Logging","itemId: " + position);
                remove(position);
                notifyDataSetChanged();
            }
        });

        //removeImage.setTag(position);
        return itemView;
    }
};
i_A_mok
  • 2,744
  • 2
  • 11
  • 15
  • Wow, thank you so much! It works exactly as intended. If you don't mind answering the following questions, just to make sure that i understood everything correctly: (1) int pos is described as the position of the item within the adapter's data set. In other words it is exactly the position in the list, we have feeded the arrayadapter with? (2) When adding or removing itmes the overwritten method getView() is being called several times (i put in log.d()). why is that? – Hooni Aug 14 '19 at 05:04
  • 1
    (1) I always use setTag() and getTag() to pass position to listeners inside adapter, this is to avoid making **position** as final [although it works in your case]. My answer to this post: https://stackoverflow.com/questions/56875819/item-selection-with-checkbox-in-android-gridview/56928239#56928239 – i_A_mok Aug 14 '19 at 08:25
  • 1
    (2) getView() is a method that the adapter used for populate its item views, so it is called when an item/data has to be displayed. It is being called almost the same number of times as the items on the screen. If your ListView is longer than the screen, when you scroll, getView() is also called to populate the newly displayed items. You can search tutorials on **custom adapter** that may help! – i_A_mok Aug 14 '19 at 08:28