2

I'm working on app that contains a custom ListView with a remove button that I update from the main activity.

I have an issue removing a row from the ListView, although I'm deleting the right index from the custom list view and call notifyDataChanged()method, GUI does not update correctly.

Here I wrote a sample project, like my real one in the idea just more sample:

  • activity_main.xml (main layout) contains ListView only called listview.
  • listview_row.xml contains two Spinners (student name and grade), set and remove button and text view.
  • Student class contains two variables: name (String) and grade (int)

MainActivity.java:

public class MainActivity extends Activity {
    ListView listView;
    listviewAdapter adapter;
    ArrayList<Student> students = new ArrayList<>();

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

        String[] names = new String[]{"Tom", "Ben", "Gil", "Adam", "Moshe", "Adi", "Michael", "Yasmin", "Jessica", "Caroline", "Avi", "Yael"};

        students.add(new Student());
        students.add(new Student());
        students.add(new Student());

        adapter = new listviewAdapter(this, students, names);
        listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);

    }

    public void updateStatus(int position)
    {
        View convertView = listView.getChildAt(position - listView.getFirstVisiblePosition());
        TextView tvValue = (TextView) convertView.findViewById(R.id.tv_Value);
        Spinner spName = (Spinner) convertView.findViewById(R.id.spNames);
        Spinner spGrade = (Spinner) convertView.findViewById(R.id.spGrades);

        tvValue.setText(spName.getSelectedItem().toString() + " got " + spGrade.getSelectedItem().toString());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_Add)
        {
            students.add(new Student());
            adapter.notifyDataSetChanged();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

listviewAdapter.java

public class listviewAdapter extends BaseAdapter
{
    public Activity context;
    public LayoutInflater inflater;

    private ArrayList<Student> studentID;
    private String[] studentsNames;

    public listviewAdapter(Activity context, ArrayList<Student> students, String[] names)
    {
        super();
        studentID = students;
        studentsNames = names;

        this.context = context;
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

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

    @Override
    public Object getItem(int position) {
        return studentID.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }


    @Override
    public int getViewTypeCount() {
        return studentID.size() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    public class ViewHolder {
        Spinner spNames, spGrades;
        TextView tvValue;
        Button btnSet, btnRemove;
    }

    @Override
    public View getView(int i, View view, final ViewGroup viewGroup)
    {
        final ViewHolder holder;
        if (view == null) {
            holder = new ViewHolder();
            view = inflater.inflate(R.layout.listview_row, null);

            holder.spNames = (Spinner) view.findViewById(R.id.spNames);
            holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
            holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
            holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
            holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
            view.setTag(holder);
            holder.spNames.setTag(0);
            holder.spGrades.setTag(0);
        }
        else{
            holder = (ViewHolder) view.getTag();
        }

        // pop spinner names
        ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<>
                (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
        studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        holder.spNames.setAdapter(studentsNamesAdapater);

        // pop spinner grades
        String[] grades = new String[101];
        for (int grade = 0; grade < 101; grade++)
            grades[grade] = String.valueOf(grade);

        final ArrayAdapter<String> studentsGradesAdapter = new ArrayAdapter<>
                (view.getContext(), android.R.layout.simple_spinner_dropdown_item, grades);
        studentsGradesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        holder.spGrades.setAdapter(studentsGradesAdapter);


        // select the right spNames index
        holder.spNames.setSelection((Integer) holder.spNames.getTag());
        // saving spinner index
        holder.spNames.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                holder.spNames.setTag(position);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        // select the right spGrades index
        holder.spGrades.setSelection((Integer) holder.spGrades.getTag());
        // saving spinner index
        holder.spGrades.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                holder.spGrades.setTag(position);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        // set (variable and textview)
        holder.btnSet.setTag(i);
        holder.btnSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // update studentID
                int position = (Integer) v.getTag();
                Student tmp = new Student(holder.spNames.getSelectedItem().toString(), Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
                studentID.set(position, tmp);
                ((MainActivity) context).updateStatus(position);
            }
        });


        // remove row
        holder.btnRemove.setTag(i);
        holder.btnRemove.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = (Integer) v.getTag();
                studentID.remove(position);
                //notifyDataSetChanged();
                ((MainActivity) context).adapter.notifyDataSetChanged();

                // for debug
                String dStatus = "Vector size: " + studentID.size() + "\n";
                for (int index = 0; index < studentID.size(); index++)
                    dStatus += studentID.get(index).name + " " + studentID.get(index).grade + "\n";
                Toast.makeText(v.getContext(), dStatus, Toast.LENGTH_SHORT).show();
            }
        });

        return view;
    }
}

My problem is that the GUI does not update correctly, the deleted item on the GUI still appears on the screen as you can see below:

enter image description here

Can someone guide me please on how to remove the right row also from the GUI?

EDIT

  1. I update my listview Adapter and use Tags as you answer me, not working.
  2. I also found strange issue when I'm trying to add to Student after remove one. For some reason, it "remember" the last student and return him with full data, as you can see in the update picture.

EDIT2

In case someone want to try it, I add Student class and XML sources:

Student.java

public class Student
{
    public String name;
    public int grade;

    public Student()
    {
        name = "";
        grade = 0;
    }

    public Student(String _name, int _grade)
    {
        name = _name;
        grade = _grade;
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/listView"
        android:layout_gravity="center_horizontal"
        android:dividerHeight="2dp" />
</LinearLayout>

listview_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#c4e0ff">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Spinner
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/spNames" />

        <Spinner
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:id="@+id/spGrades"
            android:layout_marginLeft="10dp" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Set Value"
            android:id="@+id/btn_setValue" />
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Medium Text"
            android:id="@+id/tv_Value" />

        <Button
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Remove"
            android:id="@+id/btn_Remove"
            android:layout_marginLeft="30dp" />

    </LinearLayout>

</LinearLayout>
AsfK
  • 3,328
  • 4
  • 36
  • 73
  • http://stackoverflow.com/questions/31666797/how-can-i-set-button-clicklistener-in-to-list-view-to-work-like-onitemclickliste/31667622#31667622 – Kaushik Aug 17 '15 at 09:34

7 Answers7

4

The problem is with

holder.spNames.setTag(position); 

and

holder.spNames.setSelection((Integer) holder.spNames.getTag());

As you are setting the tag for name in "onItemSelected()". When you delete the item you remove the item from the list of student, but what about the tag.

Let us say you deleted the item at 0.

Now, when "notifyDataSetChanged()" is called it will repopulate the listview based on the data is available. Now , here

else{
        holder = (ViewHolder) view.getTag();
    }

is getting called. When the i= 0 in getView() you will get the view of the "0 th" index of the previous populated list. Hence, the "(Integer) holder.spNames.getTag()" will point to the previous tag (i.e. 0 of the previous list) . This might be the cause of the issue.

I am posting updated code of

listviewAdapter

public class ListviewAdapter extends BaseAdapter
{
public Activity context;
public LayoutInflater inflater;
private ArrayList<Student> studentID;
private String[] studentsNames;
private boolean isDeleted;
public ListviewAdapter(Activity context, ArrayList<Student> students, String[] names)
{
    super();
    studentID = students;
    studentsNames = names;

    this.context = context;
    this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

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

@Override
public Student getItem(int position) {
    return studentID.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}




public class ViewHolder {
    Spinner spNames, spGrades;
    TextView tvValue;
    Button btnSet, btnRemove;
    int index;
}

@Override
public View getView(int i, View view, final ViewGroup viewGroup)
{
    final ViewHolder holder;
    if (view == null) {
        holder = new ViewHolder();
        view = inflater.inflate(R.layout.listview_row, null);

        holder.spNames = (Spinner) view.findViewById(R.id.spNames);
        holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
        holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
        holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
        holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
        Log.e("IAM", "CALLED");
        view.setTag(holder);
        //holder.spNames.setTag(0);
        //holder.spGrades.setTag(0);
    }
    else{
        holder = (ViewHolder) view.getTag();
    }
    holder.index=i;
   if(isDeleted){
       holder.tvValue.setText(getItem(holder.index).getName()+ " got " + getItem(holder.index).getGrade()); 
            }
    // pop spinner names
    ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<String>
            (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
    studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    holder.spNames.setAdapter(studentsNamesAdapater);

    // pop spinner grades
    String[] grades = new String[101];
    for (int grade = 0; grade < 101; grade++)
        grades[grade] = String.valueOf(grade);

    final ArrayAdapter<String> studentsGradesAdapter = new ArrayAdapter<String>
            (view.getContext(), android.R.layout.simple_spinner_dropdown_item, grades);
    studentsGradesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    holder.spGrades.setAdapter(studentsGradesAdapter);


    // select the right spNames index
    //holder.spNames.setSelection((Integer) holder.spNames.getTag());
    holder.spNames.setSelection(getItem(holder.index).getNameIndex());
    // saving spinner index
    holder.spNames.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            //holder.spNames.setTag(position);
            getItem(holder.index).setNameIndex(position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });

    // select the right spGrades index
   // holder.spGrades.setSelection((Integer) holder.spGrades.getTag());

    holder.spGrades.setSelection(getItem(holder.index).getGrageIndex());
    // saving spinner index
    holder.spGrades.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
           // holder.spGrades.setTag(position);
            getItem(holder.index).setGrageIndex(position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });

    // set (variable and textview)
    holder.btnSet.setTag(i);
    holder.btnSet.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // update studentID
            //final int position = getRowPosition(v);
            int position = (Integer) v.getTag();
           // Student tmp = new Student(holder.spNames.getSelectedItem().toString(), Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
           // studentID.set(position, tmp);
            getItem(position).setName(holder.spNames.getSelectedItem().toString());
            getItem(position).setGrade(Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
            holder.tvValue.setText(getItem(position).getName()+ " got " + getItem(position).getGrade());
            //((MainActivity) context).updateStatus(position);
        }
    });


    // remove row
    holder.btnRemove.setTag(i);
    holder.btnRemove.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //final int position = getRowPosition(v);
            int position = (Integer) v.getTag();
            studentID.remove(position);
            notifyDataSetChanged();
            //((MainActivity) context).adapter.notifyDataSetChanged();
            isDeleted=true;
            // for debug
            String dStatus = "Vector size: " + studentID.size() + "\n";
            for (int index = 0; index < studentID.size(); index++)
                dStatus += studentID.get(index).name + " " + studentID.get(index).grade + "\n";
            Toast.makeText(v.getContext(), dStatus, Toast.LENGTH_SHORT).show();
        }
    });

    return view;
}

public void printS() {
    for (int i = 0; i <studentID.size(); i++) {
        Log.e("NAME", ""+studentID.get(i).getName());
        Log.e("GRADE", ""+studentID.get(i).getGrade());
    }
}
}

and

Student

public class Student {
public String name;
public int grade;
private int nameIndex;
private int grageIndex;

public Student()
{
    name = "";
    grade = 0;
}

public Student(String _name, int _grade)
{
    name = _name;
    grade = _grade;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getGrade() {
    return grade;
}

public void setGrade(int grade) {
    this.grade = grade;
}

public int getNameIndex() {
    return nameIndex;
}

public void setNameIndex(int nameIndex) {
    this.nameIndex = nameIndex;
}

public int getGrageIndex() {
    return grageIndex;
}

public void setGrageIndex(int grageIndex) {
    this.grageIndex = grageIndex;
}
}
avinash
  • 1,744
  • 24
  • 40
  • Really really thank you very very very much !!!! I have to wait 3 hours til I can award the bounty, it's will come !! Thanks again!! – AsfK Aug 20 '15 at 05:01
  • btw, I found issue, the app crash after adding 4 students, The solution: don't override `getViewTypeCount` and `getItemViewType` – AsfK Aug 24 '15 at 07:09
  • @AsfK : Will remove from the solution. Thanks for highlight. – avinash Aug 24 '15 at 08:56
2

I suggest that you use setOnItemClickListener method on your list view. There are some advantages :

  • You won't have to create a new OnClickListener each time your item is rendered
  • You have a direct access to the position of the view and to the view itself : onItemClick(AdapterView<?> parent, View view, int position, long id)

You can then use that position to remove you item from your adapter.

Vadim Caen
  • 1,526
  • 11
  • 21
1

Basically this problem happens because of View recycling. Add these two methods to your adapter class

 @Override
 public int getViewTypeCount() {
     return studentID.size() + 1;
 }

 @Override
 public int getItemViewType(int position) {
     return position;
 }

Here is link which explains why you have to add these two methods. Getting an issue while checking the dynamically generated checkbox through list view

I hope it helps!

Community
  • 1
  • 1
Rajesh Jadav
  • 12,801
  • 5
  • 53
  • 78
0

you need to set tag as position for button each time you create, then inside onclick, get the tag, it will return you the correct position

inside your getView

holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
holder.btnRemove.setTag(position);

and then onClick(View view)

int position = (Integer) ((Button) view).getTag();
//remove item from position, and do the stuff here

then try to remove the item from position.

alternate solution:

Blue_Alien
  • 2,148
  • 2
  • 25
  • 29
  • Thank you. But I haven't issue with the position, it's right and I'm removing the right student from the ArrayList. Just the GUI not update correctly. you can see this in the image. – AsfK Aug 18 '15 at 11:38
0

Change:

    @Override
        public View getView(int final position, View view, final ViewGroup viewGroup)
        {
            final ViewHolder holder;
            if (view == null) {
                holder = new ViewHolder();
                view = inflater.inflate(R.layout.listview_row, null);

                holder.spNames = (Spinner) view.findViewById(R.id.spNames);
                holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
                holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
                holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
                holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
               view.setTag(viewHolder);

            }
            else{
                holder = (ViewHolder) view.getTag();
            }

    // pop spinner names
                ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<>
                        (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
                studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                holder.spNames.setAdapter(studentsNamesAdapater);

                // pop spinner grades
                String[] grades = new String [101];
                for (int grade = 0; grade < 101; grade++)
                    grades[grade] = String.valueOf(grade);

                final ArrayAdapter<String> studentsGradesAdapater = new ArrayAdapter<>
                        (view.getContext(), android.R.layout.simple_spinner_dropdown_item, grades);
                studentsGradesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                holder.spGrades.setAdapter(studentsGradesAdapater);

                // set (variable and textview)
                holder.btnSet.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // update studentID
                        Student tmp = new Student(holder.spNames.getSelectedItem().toString(), Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
                        studentID.set(position, tmp);
                        ((MainActivity) context).updateStatus(position);
                    }
                });

                // remove row
                holder.btnRemove.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v)
                    {
                        studentID.remove(position);
                        notifyDataSetChanged();

                        // for debug
                        String dStatus = "Vector size: " + studentID.size() + "\n";
                        for (int index = 0; index < studentID.size(); index++)
                            dStatus += studentID.get(index).name + " " + studentID.get(index).grade + "\n";
                        Toast.makeText(v.getContext(), dStatus, Toast.LENGTH_SHORT).show();
                    }
                });

            return view;
        }
dieter_h
  • 2,707
  • 1
  • 13
  • 19
  • Thank you very much. I just want to add for others users, after you do that spinners values will reset. you have to save them manually, see this: http://stackoverflow.com/questions/14258866/spinner-reset-value-when-scrolling-in-a-custom-listview-with-arrayadapter – AsfK Aug 17 '15 at 12:24
  • Sorry, but it's was my fault. it's not working. still the wrong student remove from the listview on the GUI. – AsfK Aug 18 '15 at 09:55
0

simply get the position of the item to be deleted on click and then remove it from your arraylist using arraylist.remove(position) and then call notifyDataSetChanged.

Neha Tyagi
  • 3,821
  • 3
  • 15
  • 25
0

You are doing it wroing, look at below codes.

@Override
public View getView(int i, View view, final ViewGroup viewGroup)
{
     if (view == null){
         view == LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.xxx, null);
         ViewHolder holder = new ViewHolder();
         holder.spNames = (Spinner) view.findViewById(R.id.spNames);
         holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
         holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
         holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
         holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
         view.setTag(holder);

     }else{
         holde = view.getTag();
     }

     /** You have to redefine each view every time because it can be recycled by the listview **/
     ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<>
                (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
        studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        holder.spNames.setAdapter(studentsNamesAdapater);
     /** so no **//


     /** you can set the position to the button as a tag **/
     holder.btnRemove.setTag(i);

     /** you MUST set the button OnClickListener after the view holder is created. you MUST NOT set the listener inside the if (view==null) pattern. **/
    holder.btnRemove.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                int position = (Integer)v.getTag(); //Get the position which you would like you remove from the button.
                studentID.remove(position);
                notifyDataSetChanged();


            }
        });
}

You may want to know why you have to dealing ListViews like this, I can't tell you why at this point, but you must HAVE to HAVE a full understanding of how AdapterViews(like ListView, GridView, RecyclerView) work if you want to be a brilliant mobile (Android, iOS and other mobile device are the same way) developer.

Look at this like: http://developer.android.com/guide/topics/ui/layout/listview.html

Welcome to the coding world, you still have a long way to run. Good luck.