0

I have a PopupWindow that contains a RecyclerView. The RecyclerView's last element is a button that adds a new item to the end of the adapter's list when clicked.

The problem: During the first time my PopupWindow has been launched the button successfully adds new items to the RecyclerView with notifyItemInserted(dataSize - 1) when clicked, but the RecyclerView doesn't update and show them. If I close and re-open the PopupWindow however, the items previously added are properly shown in the RecyclerView and it properly updates and animates new items being added to its adapter.

The Question: I'm not sure why the RecyclerView doesn't refresh and show the newly added items on first run of the PopupWindow, but works perfectly from second run onward. How do I make it work during the first run of the PopupWindow?

P.S. Its worth noting that if I use notifyDataSetChanged() the RecyclerView works correctly (displays new items) even on first launch of the PopupWindow. I want to find a way to make notifyItemInserted() work however, because it has nice animations when new items are added.

UserChordsAdapter.java

public class UserChordsAdapter extends RecyclerView.Adapter<UserChordsAdapter.ChordViewHolder> {

private Context context;
private final ListItemClickListener mClickHandler;
private ArrayList<String> mChordData = new ArrayList<String>();    //contains all user created chords as comma delimited note #s

/**
 * The interface that receives onClick messages.
 */
public interface ListItemClickListener {
    void onListItemClick(int clickedItemIndex);
}

/**
 *
 * @param clickHandler The on-click handler for this adapter. This single handler is called
 *                     when an item is clicked.
 */
public UserChordsAdapter(ListItemClickListener clickHandler) {
    mClickHandler = clickHandler;
}

@Override
public ChordViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    context = parent.getContext();
    int layoutIdForListItem = R.layout.user_chord_list_item;
    int layoutIdForFooterItem = R.layout.user_chord_add_new;
    LayoutInflater inflater = LayoutInflater.from(context);
    boolean shouldAttachToParentImmediately = false;

    View listItem;
    ChordViewHolder viewHolder;

    if (viewType == R.layout.user_chord_list_item) {    //inflate chord item
        listItem = inflater.inflate(layoutIdForListItem, parent, shouldAttachToParentImmediately);
        viewHolder = new ChordViewHolder(listItem);
    }
    else {                                               //inflate "+ Add new" button (last list item)
        listItem = inflater.inflate(layoutIdForFooterItem, parent, shouldAttachToParentImmediately);
        viewHolder = new ChordViewHolder(listItem);
    }

    return viewHolder;
}

@Override
public void onBindViewHolder(ChordViewHolder holder, int position) {
    if (position == mChordData.size()){
        holder.mAddChordButton.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                mChordData.add("1,30,40");
                notifyItemInserted(mChordData.size()-1);
            }
        });
    }
    else {
        holder.mChordName.setText("Chord " + Integer.toString(position));
    }
}

@Override
public int getItemCount() {
    if (mChordData == null){
        return 1;
    }
    return mChordData.size() + 1; // +1 is for footer button (add new)
}

class ChordViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    // Will display which ViewHolder is displaying this data
    TextView mChordName;
    Button mAddChordButton;

    /**
     * Constructor for our ViewHolder. Within this constructor, we get a reference to our
     * TextViews and set an onClickListener to listen for clicks. Those will be handled in the
     * onClick method below.
     */
    public ChordViewHolder(View itemView) {
        super(itemView);

        mAddChordButton = (Button) itemView.findViewById(R.id.button_add_new);
        mChordName = (TextView) itemView.findViewById(R.id.tv_view_holder_instance);
        itemView.setOnClickListener(this);
    }

    /**
     * Called whenever a user clicks on an item in the list.
     * @param v The View that was clicked
     */
    @Override
    public void onClick(View v) {
        int clickedPosition = getAdapterPosition();
        String chordData = mChordData.get(clickedPosition);
        mClickHandler.onListItemClick(clickedPosition);
    }
}

/**
 * Distinguishes if view is a Chord list item or the last item in the list (add new chord)
 * @param position
 * @return
 */
@Override
public int getItemViewType(int position) {
    return (position == mChordData.size()) ? R.layout.user_chord_add_new : R.layout.user_chord_list_item;
}}

FragmentChordMenu.java

public class FragmentChordMenu extends Fragment implements UserChordsAdapter.ListItemClickListener{
private FloatingActionButton mFAB;
private View mPopupView;
private PopupWindow mUserChordMenu;
private RecyclerView mUserChordsList;
private UserChordsAdapter mRecyclerViewAdapter;
private int numItems = 0;   //TODO: dynamically calculate this as # of saved chords + 1(add new)

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mRecyclerViewAdapter = new UserChordsAdapter(this);
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.menu_fragment_chord, container, false);

    LayoutInflater layoutInflater = (LayoutInflater)getActivity().getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mPopupView = layoutInflater.inflate(R.layout.menu_popup_set_chords, null);
    int menuWidth = (int)(MainActivity.getActualWidth()*.95);
    int menuHeight = (int)(MainActivity.getActualHeight()*.90);
    mUserChordMenu = new PopupWindow(mPopupView, menuWidth, menuHeight);
    mUserChordMenu.setFocusable(true);

    mFAB = (FloatingActionButton) v.findViewById(R.id.addChord);
    mFAB.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            mUserChordMenu.showAtLocation(mPopupView, Gravity.CENTER, 10, 10);
            mUserChordsList = (RecyclerView) mPopupView.findViewById(R.id.rv_userChords);
            LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
            mUserChordsList.setLayoutManager(layoutManager);
            mUserChordsList.setAdapter(mRecyclerViewAdapter);
        }
    });
    return v;
}


/**
* Called from UserChordsAdapter's onClick. Only fires on list item clicks, not the add new button
 *
* */
@Override
public void onListItemClick(int clickedItemIndex) {
}}
Cody
  • 1,801
  • 3
  • 28
  • 53
  • what external changes you deriving from? – Remario May 29 '17 at 02:32
  • None. This is a proof of concept for adding placeholder items to my RecyclerView when a button is clicked. – Cody May 29 '17 at 02:40
  • why do you have the notification update notifyItemInserted(mChordData.size()-1);, in onBind? – Remario May 29 '17 at 02:58
  • Because that's where the onClick for the "add new" item button resides. An explanation for making the last item being an "add new" button can be found here: https://stackoverflow.com/questions/29106484/how-to-add-a-button-at-the-end-of-recyclerview – Cody May 29 '17 at 03:02
  • well of course it wont update, because the update logic is tied to the redrawing of the views directly. – Remario May 29 '17 at 03:04
  • So why does it update onClick when I open the PopupView for the second time in the app's lifecycle? – Cody May 29 '17 at 03:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/145330/discussion-between-remario-and-cody). – Remario May 29 '17 at 03:06

1 Answers1

0

The problem lies with the logic you use to update your views. Currently what you are saying is this, only notify my data when a view is drawn on the screen(OnBind). That is why it always work for the second try, because whenever a view is being drawn(swipe etc). that onBind method will be triggered.What you need to do is to create a method in the Adapter class, that replaces this logic.

   holder.mAddChordButton.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                mChordData.add("1,30,40");
                notifyItemInserted(mChordData.size()-1);
            }
        });

So create a method that adds item to the mChorData set object, then call notifyItemInserted(mChordData.size()-1); in that method. This will always update and notify the adapter of any changes, hence triggering redraw automatically.

First create a public method in UserChordsAdapter that accepts an mChordData for its paramter,then in that method call, notifyItemInserted(mChordData.size()-1);. Firstly you need to expose the clickListener outside of that adapter.

Remario
  • 3,813
  • 2
  • 18
  • 25
  • How am I supposed to call this new method outside of the onBind logic if my "add new" button's onClick is in onBind? Can you provide a real example? – Cody May 29 '17 at 04:20
  • sure, give me some time. – Remario May 29 '17 at 04:30
  • check updated answer, if you want code, send a gist, with more code. – Remario May 29 '17 at 13:15
  • The part that i'm still not understanding is how to "expose the clickListener" outside of the adapter, the rest is trivial. Can you show that part? If I just make a method that's called from within the current onClick listener nothing is really changed of course, its just shuffling around the location of those two lines within onClick. – Cody May 29 '17 at 15:58