0

I'm beginner in programming in Android Studio and I'm making now some kind of messenger via bluetooth. So I have my own ArrayAdapter class which extends ArrayAdapter class and it is for outgoing and incoming messages. I want incoming messages to be at the left side ang outgoing ones at the right, so I made two layouts for this Adapter. I know, that on stackoverflow there is a lot of solutions to make ArrayAdapter with few diffrent layouts for each row, but every one of them doesn't work - changing layouts cause change view of every row. So my solution is to make another ArrayList of booleans, and in getView() I check what I have in this List - true or false - and use right layout on that row in ArrayAdapter (I'm checking it by position field from getView()). And when I send a lot of messages to second device and try to response to first device there is NullPointerException in line with (TextView)row.findViewById(R.id.singleIncomingMessage); or

(TextView)row.findViewById(R.id.singleOutgoingMessage);

This exceptions seems to appear in random situation, but of course there must be some pattern. Here's the whole code. What it's wrong? And I'm sorry for my language if there is some misspells ;)

public class MyArrayAdapter extends ArrayAdapter<String> {
    public MyArrayAdapter(Context context, ArrayList<String> list) {
        super(context, 0, list);
        this.list =list;
        this.context=context;
    }

    ArrayList<String> list;
    Context context;

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

    @Override
    public String getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
    @Override
    public int getItemViewType(int position) {
        if(Messenger.inOut){
            return 1;
        }
        else{
            return 0;
        }
    }
    @Override
    public int getViewTypeCount() {
        return 2;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        int type=getItemViewType(position);
        View row=convertView;
        if(row==null){
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if(Messenger.inOutList.get(position)==0){
                row=inflater.inflate(R.layout.outgoing_message_layout, parent, false);
            }
            if(Messenger.inOutList.get(position)==1){
                row=inflater.inflate(R.layout.incoming_message_layout, parent, false);
            }
        }
        String message=getItem(position);
        TextView label;
        if(Messenger.inOutList.get(position)==0){
            label=(TextView)row.findViewById(R.id.singleOutgoingMessage);
            label.setText(message);
        }
        if(Messenger.inOutList.get(position)==1){
            label=(TextView)row.findViewById(R.id.singleIncomingMessage);
            label.setText(message);
        }
        return row;
    }
}
  • Is there ever a situation where `Messenger.inOutList.get(position)` is neither 0 nor 1? That's the only situation where `row` would still be `null` when you try to access your views. – Michael Dodd Jul 06 '18 at 20:34
  • It could have an invalid value in the first check, then a valid one when you go to access the views. Personally I'd copy the value of `Messenger.inOutList.get(position)` to a local `int` variable so there's no chance of the value changing mid-function – Michael Dodd Jul 06 '18 at 20:35
  • Why do you even have singleton lists. There might be racing condition and that causes your problem. It's usually racing condition when random problems occur. – TheKarlo95 Jul 06 '18 at 21:22
  • I posted an answer, but I'm not sure if it's really how you want to use this from your question. Do you have a single list with different views in it, or two side-by-side lists you want to use the same adapter code for? Can you post how you're using the adapter (your `onCreate` calls)? – Tyler V Jul 06 '18 at 23:08

2 Answers2

0

I think the issue is from the case where row is non-null. It may have previously been inflated as an outgoing message layout, now you are recycling it and trying to treat it as an incoming message layout, so it can't find the TextView.

It's hard to say for sure this is the issue though since I don't really know how the Messenger.* calls behave in your code. Since you already get type in getView you should use that rather than Messenger.inOutList.get(position)==X to determine which view logic to use if you keep it this way. This question has some good answers on how to do this consistently.

Also keep in mind that for this to work getItemViewType must always return the same type for a given position, or else you have to detect the change and inflate a new layout in getView. If Messenger.inOut is constant, there's not reason to use this format (multi-layout format). If it's not a constant and it gets changed, then you need to detect this in getView

Tyler V
  • 9,694
  • 3
  • 26
  • 52
0

Okay, thank you very much, I finally did it. I was using two list (of booleans and messages) and after I read about ArrayAdapter I decided to make class with this two fields and make a list of its object. That error was probably because of that second list of booleans. Here's below my final code if anyone have similiar problem.

public class MyArrayAdapter extends ArrayAdapter<MessageWithType> {
public MyArrayAdapter(Context context, ArrayList<MessageWithType> list) {
    super(context, 0, list);
    this.list =list;
    this.context=context;
}
public static final int TYPE_OUT = 0;
public static final int TYPE_IN = 1;

ArrayList<MessageWithType> list;
Context context;

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

@Override
public MessageWithType getItem(int position) {
    return list.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}
@Override
public int getItemViewType(int position) {
    if(list.get(position).inOut){
        return 1;
    }
    else{
        return 0;
    }
}
@Override
public int getViewTypeCount() {
    return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    MessageWithType item=getItem(position);
    int type=getItemViewType(position);
    View row=convertView;
    ViewHolder viewHolder=null;
    if(row==null){
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if(type==TYPE_OUT){
            row=inflater.inflate(R.layout.outgoing_message_layout, parent, false);
        }
        if(type==TYPE_IN){
            row=inflater.inflate(R.layout.incoming_message_layout, parent, false);
        }
        TextView label=(TextView)row.findViewById(R.id.singleMessage);
        viewHolder=new ViewHolder(label);
        row.setTag(viewHolder);
    }
    else{
        viewHolder=(ViewHolder)row.getTag();
    }
    viewHolder.textView.setText(item.message);

    return row;
}
public class ViewHolder{
    TextView textView;
    public ViewHolder(TextView textView){
        this.textView=textView;
    }
}

}