简体   繁体   中英

How do I update my RecyclerView button state on click after alert dialog confirmation in activity/fragment?

I have a pretty unique situation, where I have a button in a recyclerview, which upon click (initial state "register"), passes an intent to a broadcast receiver in fragment or activity where it throws an alert dialog , with 2 options, yes or no. If no is selected, nothing happens and the dialog dismisses, but if yes is clicked, it processes a function I defined in my presenter class (related to data) and is supposed to update my button ui state to "cancel". and same goes for the other way around where upon clicking cancel it will bring back alert, and clicking on yes will switch the ui text to register.

Now I have implemented the code as follows.(please note, even notifydatasetchanged doesnt work for me). Any idea what I am doing wrong and how I can achieve this? code in my public void onBind(int position) function in adapter:

if (repo.getIsRsvpAvailable().equals("true")) {
    rsvpButton.setVisibility(View.VISIBLE);
    for (int i = 0; i < mAllRSVPEventsList.size(); i++) {
        if (mAllRSVPEventsList.get(i).getEvent().getEventId().equals(repo.getEventId()) && mAllRSVPEventsList.get(i).getIsAttending()) {
            rsvpButton.setText("CANCEL");
            rsvpButton.setOnClickListener(v -> {
                Intent in = new Intent("main_rsvp_button_clicked");
                in.putExtra("main_rsvp_event_id", repo.getEventId());
                in.putExtra("main_rsvp_is_attending", "false");
                in.putExtra("main_rsvp_item_position", position);
                rsvpButton.getContext().sendBroadcast(in);
            });
            break;
        } else {
            rsvpButton.setText("RSVP");
            rsvpButton.setOnClickListener(v -> {
                Intent in = new Intent("main_rsvp_button_clicked");
                in.putExtra("main_rsvp_event_id", repo.getEventId());
                in.putExtra("main_rsvp_is_attending", "true");
                in.putExtra("main_rsvp_item_position", position);

                rsvpButton.getContext().sendBroadcast(in);
            });
        }
    }

}

Here's the corresponding code in broadcast receiver in my activity :

private BroadcastReceiver mEventIdReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            String eventId = intent.getStringExtra(EVENTID_MAIN_EXTRA_TITLE);
            String isAttending = intent.getStringExtra(EVENTID_MAIN_IS_ATTENDING);
            int itemPosition = intent.getIntExtra(EVENTID_MAIN_RSVP_ITEM_POSITION, 0);


            if (isAttending.equals("true")) {
                showDialog(R.string.rsvp_title, R.string.confirm_rsvp_body, R.string.yes,
                        (dialog, which) -> {
                            mPresenter.onRSVPClick(eventId, isAttending);

                            mEventListAdapter.notifyDataSetChanged();
                            mEventRecyclerView.removeAllViews();
                            mEventRecyclerView.scrollToPosition(itemPosition);

                        }, R.string.no, null, null);
            } else {
                showDialog(R.string.rsvp_title, R.string.confirm_cancel_body, R.string.yes,
                        (dialog, which) -> {
                            mPresenter.onRSVPClick(eventId, isAttending);

                            mEventListAdapter.notifyDataSetChanged();
                            mEventRecyclerView.removeAllViews();
                            mEventRecyclerView.scrollToPosition(itemPosition);

                        }, R.string.no, null, null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};

Please note: mEventListAdapter is my adapter i am using the button UI code in and mEventRecyclerView is the recycler view i am using in the fragment.

Any idea what I am missing or doing wrong? Thanks!

RSVPClick method :

@Override
public void onRSVPClick(String eventId, String isAttending) {
    getMvpView().showLoading();
    getCompositeDisposable().add(getDataManager()
            .doRSVPEventApiCall(
                    eventId,
                    getDataManager().getFirstName(),
                    getDataManager().getLastName(),
                    getDataManager().getCurrentUserEmail(),
                    isAttending
            )
            .subscribeOn(getSchedulerProvider().io())
            .observeOn(getSchedulerProvider().ui())
            .subscribe(response -> {
                if (!isViewAttached()) {
                    return;
                }
                getMvpView().hideLoading();
            }, throwable -> {
                if (!isViewAttached()) {
                    return;
                }
                getMvpView().hideLoading();
                if (throwable instanceof ANError) {
                    ANError anError = (ANError) throwable;
                    handleApiError(anError);
                }
            }));
}

You can easily maintain an array to indicate the state of the buttons of each of your items in the RecyclerView . For example, let us assume there are two states of each button (ie RSVP = 0, Cancel = 1). Now declare an array in your RecyclerView adapter like the following.

private int[] stateArray; 

In the constructor of your adapter, initialize the array like the following. Let us assume your constructor looks like the following.

public EventListAdapter(Context context, ArrayList<Event> eventList) {
    this.eventList = eventList; 
    stateArray = new int[eventList.size];
    initializeTheStateArray(eventList);
}

private void initializeTheStateArray(ArrayList<Event> eventList) {

    // Initialize the array so that we can keep track of the items which are being attended or not. 
    for (int i = 0; i < eventList.size(); i++) {
        if(event.isAttending()) stateArray[i] = 1;
        else stateArray[i] = 0;
    }
}

Now in the onBindViewHolder function, set the text of the button based on the entry of the stateArray . This should look somewhat like the following.

if(stateArray[position] == 1) button.setText("Cancel"); // Because this item is already registerd 
else button.setText("RSVP");

You need some additional function in your adapter so that you can update the stateArray on your button click or from an update from the API.

public void updateButtonState(int position, boolean isAttendening) {
    if(isAttending) stateArray[position] = 1;
    else stateArray[position] = 0;
    notifyDataSetChanged(); // Call the notifyDataSetChanged here to see the affect
}

And when you update the whole list of your adapter after an API call, do not forget the update the stateArray as well. I hope you have a function to update the event list in your adapter already. Modify the function like the following.

public void updateEventList(ArrayList<Event> eventList) {
    this.eventList = eventList;
    stateArray = new int[eventList.size()];
    initializeTheStateArray(eventList);
}

Now you can call updateButtonState function of your adapter when the button is clicked. Modify the button click action in your rsvpButton.setOnClickListener .

You can also modify the onReceive function if necessary to get the expected output in your RecyclerView .

Hope this setup will help you to achieve the expected behavior that you want.

Last, but not least, in your onReceive function, you are immediately updating the RecyclerView when it will have no effect on the items of the RecyclerView because the network call is asynchronous and will take some time to fetch the data from the API. You might consider calling the updateEventList method from the onRSVPClick method when the data is available from the API call.

Hope that helps!

I think that perhaps this might help:

  1. Set click listener on the button in onBindViewHolder() .
  2. Inside your click listener call notifyItemChanged(position)
  3. Maintain some state in your adapter to handle the logic for when to change the buttons state, for instance, have a var in the onClick you can flag like shouldChangeState = true
  4. When on onBindViewHolder() is called again, check this state and bind as you would usually only handle this scenario and change TextView accordingly. button.setVisibility(..) or button.text = "My New Text"

Inside onBindViewholder :

 holder.myRow.setOnClickListener(v -> {
          notifyItemChanged(position) 
 }

Ok, so to handle your holder inside your onBindViewHolder (In Kotlin Sorry):

Create your view holder.

sealed class MyListViewHolder(view: View) : RecyclerView.ViewHolder(view)

class MyListItemViewHolder(view: View) : MyListViewHolder(view) {
    val name: TextView = view.my_name
    val myDetail: TextView = view.my_detail
    val myOtherDetail: TextView = view.my_other_detail
}

In on create:

 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyListViewHolder {
        val inflater: LayoutInflater = LayoutInflater.from(parent.context)
        val view = inflater.inflate(R.layout.row_item, parent, false)
        val viewHolder = MyListItemViewHolder(view)

        with(view) {
            tag = viewHolder
        }
        return viewHolder
    }

Then in onBindViewHolder:

  override fun onBindViewHolder(itemHolder: MyListViewHolder, position: Int) {
        val holder = itemHolder as MyListItemViewHolder
        val currentInfo = myList[position]

        // Get your data from list to bind here.

        holder.name.text = // Text
        holder.myDetail.text = // Text
        holder.myOtherDetail.text = Text

        holder.setOnClickListener {
             // Other stuff, state, actions etc.
             notifyItemChanged(position)
        }
    }

notifyDatasetChanged() should be called after the data set is actually changed. As Sudhanshu Gupta pointed out notifyDatasetChanged() is called at the wrong time (when the dialog button is clicked). Since your mEventListResponseList and mAllRSVPEventsList in the adapter are private the only way to change them is to call addItems() and addRSVPItems() methods. Clearly at the time of dialog button click, none of these methods are called indicating data set is not changed. So notifyDatasetChanged() has no effect.

it might take a few seconds to update. but regardless of whether it updates or not, is it possible to change the ui of the button in adapter to a different state (say cancel from rsvp) once alert appears and yes is clicked

No, because of the above reason.

And, when the response arrives it is discarded except for logging. Snippet from onRSVPClick() in the Presenter class.

.subscribe(response -> {
    if (!isViewAttached()) {
        return;
    }
    Log.d("rsvptest", "basic event id:" + response);

    getMvpView().hideLoading();

}

To see the change, you should somehow update mEventListResponseList and mAllRSVPEventsList in the adapter by exposing a suitable method and call it with the data from the arrived response. Also don't forget to call notifyDatasetChanged() in that method too.

So, I was able to come up with an answer and was pretty straightforward.

Here's the answer:

if (repo.getIsRsvpAvailable().equals("true")){
    rsvpButton.setVisibility(View.VISIBLE);
    if(mAllRSVPEventsList.size() != 0) {

        for (int i = 0; i < mAllRSVPEventsList.size(); i++) {
            if (mAllRSVPEventsList.get(i).getEvent().getEventId().equals(repo.getEventId()) && mAllRSVPEventsList.get(i).getIsAttending()) {
                rsvpButton.setText("CANCEL");
                rsvpButton.setTextColor(Color.parseColor("#647ed6"));
                mYesText.setVisibility(View.VISIBLE);
                rsvpButton.setBackgroundColor(Color.WHITE);
                GradientDrawable gd = new GradientDrawable();
                gd.setCornerRadius(2);
                gd.setGradientRadius(2);
                gd.setStroke(2, 0xFF647ed6);
                rsvpButton.setBackground(gd);
                rsvpButton.setOnClickListener(v -> {
                    Log.d("clickedbutton", "it is in" + repo.getTitle());
                    mCallback.onRSVPButtonClick(repo.getEventId(), position, "false", repo.getTitle(), rsvpButton, mAllRSVPEventsList);

                });
                break;
            }

            if (mAllRSVPEventsList.get(i).getEvent().getEventId().equals(repo.getEventId()) && !mAllRSVPEventsList.get(i).getIsAttending()) {

                rsvpButton.setText("RSVP");
                rsvpButton.setBackgroundColor(Color.parseColor("#647ed6"));
                rsvpButton.setTextColor(Color.WHITE);
                mYesText.setVisibility(View.GONE);

                rsvpButton.setOnClickListener(v -> {

                    mCallback.onRSVPButtonClick(repo.getEventId(), position, "true", repo.getTitle(), rsvpButton, mAllRSVPEventsList);

                });
                break;
            } else {
                rsvpButton.setText("RSVP");
                rsvpButton.setBackgroundColor(Color.parseColor("#647ed6"));
                rsvpButton.setTextColor(Color.WHITE);
                mYesText.setVisibility(View.GONE);
                rsvpButton.setOnClickListener(v -> {
                    mCallback.onRSVPButtonClick(repo.getEventId(), position, "true", repo.getTitle(), rsvpButton, mAllRSVPEventsList);

                });
            }


        }
    } else {
        for (int i = 0; i < mEventListResponseList.size(); i++) {
            rsvpButton.setOnClickListener(v -> {
                mCallback.onRSVPButtonClick(repo.getEventId(), position, "true", repo.getTitle(), rsvpButton, mAllRSVPEventsList);

            });
        }
    }
}

So, basically I had to separate out the if loop so that the ui updates based on conditions after refresh. Works every time without any issue and does check if the size of the rsvp list if 0, if it is(set to rsvp by default, clicking on which will switch it to cancel button in UI), it adds the event to the rsvp list so next time I iterate, it has the event to cross check against. Thanks to all for trying to help with this, +10 for all those who answered! I appreciate your help.

Are you updating the repo object based on user selected action on the dialog before calling notifyDatasetChanged() ? Looks like the doRSVPEventApiCall() method does not update the list of items available to the adapter thus notifyDatasetChanged() has no effect.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM