Search code examples
androidandroid-arrayadapter

Custom arrayAdapter only returning 8 objects


I have an ArrayList with 11 objects, when put it into a Listview by extending a custom ArrayAdapter, it only shows 8 objects, from 9, 10 and 11 is duplicated 1, 2, 3 with content.

When I call System.out.println("Position: " + position); with the int position from the SMS2PeopleAdapter class, it would only show 8 items with positions from 10, 9, 8, ... 3.

Could you help me solve this problem? Thanks.

Activity:

public class SMS2PeopleActivity extends AppCompatActivity {

    ListView lv;
    SMS2PeopleAdapter sms2PeopleAdapter;
    ArrayList<SMS> listSMS2People;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sms2people);
        lv = (ListView) findViewById(R.id.list_view_messages);

        listSMS2People = new ArrayList<>();
        listSMS2People.add(new SMS("1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1"));
        listSMS2People.add(new SMS("2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2"));
        listSMS2People.add(new SMS("3", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3"));
        listSMS2People.add(new SMS("4", "4", "4", "4", "4", "4", "4", "4", "4", "4", "4", "4", "4", "4", "4", "4", "4"));
        listSMS2People.add(new SMS("5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5"));
        listSMS2People.add(new SMS("6", "6", "6", "6", "6", "6", "6", "6", "6", "6", "6", "6", "6", "6", "6", "6", "6"));
        listSMS2People.add(new SMS("7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7"));
        listSMS2People.add(new SMS("8", "8", "8", "8", "8", "8", "8", "8", "8", "8", "8", "8", "8", "8", "8", "8", "8"));
        listSMS2People.add(new SMS("9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9"));
        listSMS2People.add(new SMS("10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10"));


        sms2PeopleAdapter = new SMS2PeopleAdapter(this, listSMS2People);
        lv.setAdapter(sms2PeopleAdapter);
    }
}

My custom ArrayAdapter:

public class SMS2PeopleAdapter extends ArrayAdapter<SMS> {
    Activity activity;

    public SMS2PeopleAdapter(Activity activity, ArrayList<SMS> products) {
        super(activity, 0, products);
        this.activity = activity;
    }

    public View getView(int position, View convertView, ViewGroup viewGroup) {

        if (convertView == null) {
            System.out.println("Position: " + position);
            SMS sms = (SMS) getItem(position);
            LayoutInflater inflater = activity.getLayoutInflater();
            if (position % 2 == 0) {
                convertView = inflater.inflate(R.layout.list_item_message_left, null);
                TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg);
                txtMsg.setText(sms.getBody());
                System.out.println(position + " Position: " + sms.getBody());
            } else {
                convertView = inflater.inflate(R.layout.list_item_message_right, null);
                TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg);
                txtMsg.setText(sms.getBody());
                System.out.println(position + " Position: " + sms.getBody());
            }
        }
        return convertView;
    }
}

Solution

  • As nPn indicated, you are only handling the case where the convertView parameter is null. This only happens when the view that is passed to your adapter has not yet been created. As soon as you start scrolling through your list, Android will pass old views that scrolled off of the screen back to your adapter to be used for the "new" view that is coming into view at the bottom of the screen. This minimizes resources by only using as many views as needed to fill the screen.

    So all you need to do is modify your code so that it only inflates a new view from your layout resource when the convertView parameter is null. After that, it's all the same code.

    Actually, now that I've inspected your code, it appears that you have two different layout types - a "left" and a "right". The right way to do it is to implement two different View Types for your list, and override getItemViewType() to return the proper type based on it's position.

    Also, since you don't know if the passed convertView is of the type you need or not, the easiest thing to do is just create a new view each time. This will work as long as you don't have a huge list and spend all day scrolling back and forth (it will consume more resources each time a new list item appears on the screen). If that is a concern, follow this SO post to learn how to replace an existing view with another view without creating a new one.

    Suggested modification shown below. Feel free to change the viewTypes to enums for cleaner code.

    public View getView(int position, View convertView, ViewGroup viewGroup) {
    
    
        System.out.println("Position: " + position);
        SMS sms = (SMS) getItem(position);
    
        int viewType = getItemViewType(position);
    
        switch (viewType) {
            case 1: { //LEFT
                     LayoutInflater inflater = activity.getLayoutInflater();
                     convertView = inflater.inflate(R.layout.list_item_message_left, null);
                break;
            }
            case 2: { //RIGHT
                     LayoutInflater inflater = activity.getLayoutInflater();
                     convertView = inflater.inflate(R.layout.list_item_message_right, null);
                break;
            }
        }
    //Convert view is now garunteed to not be null, and to be of the correct type, so now just populate your data.                
        TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg);
        txtMsg.setText(sms.getBody());
        System.out.println(position + " Position: " + sms.getBody());
    
        return convertView;
    }
    
    @Override
    public int getItemViewType(int position) {
      if (position % 2 == 0) 
        return 1;  //LEFT
      else
        return 2;  //RIGHT
    }