Hoang
Hoang

Reputation: 857

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;
    }
}

Upvotes: 0

Views: 97

Answers (2)

rothloup
rothloup

Reputation: 1290

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
}

Upvotes: 1

nPn
nPn

Reputation: 16748

In the getView method of your adapter, you need to handle the case were the convertView is not null, I think you are just returning the view passed into it.

You could probably get past this by not checking the convertView at all and just returning a new view each time. This would not be ideal, but would work. Then you can look into the viewHolder pattern, or look at the new RecyclerView which is a new variation and makes it even easier.

What is actually happening is this. getView() is were the system is asking you to provide a view for display, as part of the call it potentially provides you with an "old" view which is no longer being displayed. You have the option or reusing this view if you like, or just inflating a brand new view, filling it in and returning that. What you are doing is inflating a new view when the system does not give you a old view, but if it does give you an old view you just return it without filling it out with the new info for that particular row, so you just see the old view for those rows.

Upvotes: 1

Related Questions