2

I want to be able to add items to my ReclycerView dynamically.

When an item loads -> setText() -> I add another item on list.

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    final Message message = mDataset.get(position);

    if(message.isAnswers()) {
        holder.mAnswer1Button.setText(message.getAnswer1());
        holder.mAnswer2Button.setText(message.getAnswer2());

        holder.mAnswer1Button.setOnClickListener(v -> {
            if(message.getChild1() > 0) {
                add(position + 1, dataListShared.get(message.getChild1()));
                holder.mAnswer1Button.setClickable(false);
                holder.mAnswer2Button.setEnabled(false);
            }
        });
    } else {
        holder.mMessageTextView.setText(message.getMessage());
            if(message.getChild1() > 0) {
                add(position + 1, dataListShared.get(message.getChild1()));
                holder.mMessageTextView.setEnabled(false);
            }
    }
}

This is what I have inside onBindViewHolder. When I am on the first case if(), and I click the button, the item is added to the list. On the second Case else(), I would like for the text to be set on this current item and than already add another one.

How can I achieve this?

Moreover, why add() works inside onClickListener but not outside of it?

The error I get is:

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

Thanks! :)

Icarus
  • 1,627
  • 7
  • 18
  • 32
Lucas Storti
  • 88
  • 1
  • 10
  • The onClickListener is executed much later, when the View is actually displayed and clicked, while the add() in onBindViewHolder() is called immediately when binding the view. – Micha Jan 22 '17 at 01:26
  • Also, there's a later callback I would try, onViewAttachedToWindow(). Maybe adding the code there helps (I haven't actually tested this). – Micha Jan 22 '17 at 01:33
  • Thanks man, I will try that – Lucas Storti Jan 22 '17 at 02:11
  • what actually do you want to achieve? – pskink Jan 22 '17 at 07:29
  • I want to add Item (if layout has only a textView and not a button) -> add another item (if layout doesn't have a button) -> add another... until we get to an item that will have a button – Lucas Storti Jan 22 '17 at 07:32
  • inside `onBindViewHolder`? do you know when this method is called? – pskink Jan 22 '17 at 07:33
  • it doesn't have to be inside onBindViewHolder . Thats exactly my question, where would that be? How do I know when an Item was added, and than react to it? – Lucas Storti Jan 22 '17 at 07:35
  • just add it to your `mDataset` and call `notifyDataSetChanged` / `notifyItemInserted` – pskink Jan 22 '17 at 07:43
  • I tried that and get that exception. Do you know where I can see that the `RecyclerView computing a layout or scrolling` is over? – Lucas Storti Jan 22 '17 at 07:57
  • see `Recyclerview#isComputingLayout()` method – pskink Jan 22 '17 at 08:05

2 Answers2

1

The error itself is self explanatory ... It is dangerous to setOnClickListener in onBindViewHolder. This method is step of refresh each recycler item.

You should move method setOnClickListener to ViewHolder which is inner class on your adapter.

class MyViewHolder extends RecyclerView.ViewHolder
    {
        private final OnClickListener mOnClickListener = new MyOnClickListener();
        Button mAnswer1Button, mAnswer2Button;
        public MyViewHolder (View itemView) {
            super(itemView);
            mAnswer1Button = (Button) itemView.findViewById(R.id.item);
            mAnswer2Button = (Button) itemView.findViewById(R.id.item);
            mAnswer1Button.setOnClickListener(mOnClickListener);

        }


       @Override
       public void onClick(final View view) {
            //Now your Logic ....
       }
    }

One more thing you could do is set Create an interface OnItemClickListener and declare onItemClick and then make the activity from where you are setting up the adapter for the particular recycler view implment OnItemClickListener this and over there you can dynamically add another item and setAdapter again or notifyDataSetChanged()

Your InterFace

 public interface OnItemClickListener{
      public void onItemClick(int position);
 }

Your MainActivity

   class MainActivity implements OnItemClickListener{
       RecyclerView mRecyclerView ;

       @Override
    public void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.layout);
    mRecyclerView = (RecyclerView ) findViewById(R.id.recyclerview);
    mAdapter= new MyAdapter(ArrayList, getContext(), MainActivity.this);

    }

       @Overrride
       public void onItemClick(int position)
       {
               //Now your Logic ....

       } 
   } 

Now you can call this method onItemClick from the Adpater clas by setting onClickListener mAnswer1Button in the ViewHolder class and calling this method with in the onClick

Jai
  • 3,211
  • 2
  • 17
  • 26
0

As commented, I think it should work like this:

private int position;

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    this.position = position;
}


@Override
public void onViewAttachedToWindow(ViewHolder holder) {
    final Message message = mDataset.get(position);

    if(message.isAnswers()) {
        holder.mAnswer1Button.setText(message.getAnswer1());
        holder.mAnswer2Button.setText(message.getAnswer2());

        holder.mAnswer1Button.setOnClickListener(v -> {
            if(message.getChild1() > 0) {
                add(position + 1, dataListShared.get(message.getChild1()));
                holder.mAnswer1Button.setClickable(false);
                holder.mAnswer2Button.setEnabled(false);
            }
        });
    } else {
        holder.mMessageTextView.setText(message.getMessage());
            if(message.getChild1() > 0) {
                add(position + 1, dataListShared.get(message.getChild1()));
                holder.mMessageTextView.setEnabled(false);
            }
    }
}

In onBindViewHolder(), you apparently cannot change the dataset - I guess as the framework is still busy displaying the previous dataset. But when saving the position in an instance variable, and updating the dataset in onViewAttachedToWindow(), the RecylerView should be ready for more data.

To be honest though, I wouldn't add all this logic to the ViewHolder, but pass an interface to it so the logic can be kept in a more central place, like a presenter.

Micha
  • 396
  • 2
  • 8
  • `onBindViewHolder()` doesn't work also, I will try the presenter? Never heard of it... I will google it now. Thanks :) – Lucas Storti Jan 22 '17 at 20:07
  • Oh, that was confusing, sorry. Using a presenter won't help you solve this issue, it's just an architectural suggestion. Do you get the same exception when moving your logic to onViewAttachedToWindow()? – Micha Jan 22 '17 at 20:12
  • Yeep, the same exception... I don't think the RecyclerView is ready just yet – Lucas Storti Jan 22 '17 at 20:15
  • Shame. I still can't try on my own, but maybe @pskink's suggestion to rely on Recyclerview#isComputingLayout() might work. See http://stackoverflow.com/a/36913261/1430422 for an example. – Micha Jan 22 '17 at 20:33
  • Idk where I would add the `isCOmputingLayout()`? I would need a listener that says when the Item is done loading. Or like a thread that runs this every second till it hits `true`? That doesn't look like a good solution to me. If I could have a listener for everytime an Item was added, that would be perfect – Lucas Storti Jan 23 '17 at 18:39
  • Yes, something like a Handler, as in the link above. Could be off the main thread. Coming to think of it, as changing the dataset every time a view is bound will cause the view to be rebound immediately - I'm not sure that's a good idea. Delaying that with a thread that polls once a second might be better, even though I understand your reservations regarding that approach. Sorry I don't have any other ideas - I'm not aware of any such listeners in the framework. – Micha Jan 24 '17 at 08:32