0

I'm at a loss as to why this is happening. I have a recyclerview listing a set of Hour objects as checkboxes with text.

Couldn't find a way to embed videos. YouTube clip is here: https://www.youtube.com/watch?v=KI9FJSfmrXM

The movie shows a view of my Firebase RealTime Database on the left. Each item corresponds to a checkbox in my app on the right. The keys are the hours of each item in military time. So 8am is 8 and 5pm is 17, etc. When I check/uncheck an item, other items are randomly checked/unchecked also. I'm updating the entire object for that hour:

    public static class ProtocolRecyclerViewAdapter extends FirebaseRecyclerAdapter<Hour, ProtocolRecyclerViewAdapter.HourHolder> {

    private final DailyScheduleActivity mParentActivity;
    private DatabaseReference mDb;
    private String protocolUserDateKey;
    ProtocolNonMalignant mProtocol = new ProtocolNonMalignant();


    ProtocolRecyclerViewAdapter(DailyScheduleActivity parent, DatabaseReference db, String protocolKey, FirebaseRecyclerOptions<Hour> options) {
        super(options);
        mParentActivity = parent;
        mDb = db;
        protocolUserDateKey = protocolKey;
    }

    @NonNull
    @Override
    public HourHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_protocol_row, parent, false);
        return new HourHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull HourHolder holder, final int position, final Hour currentHour) {

        holder.mHourCheckBox.setText(currentHour.toString());

        String state = determineCheckboxState(currentHour);
        if (state.equals("unchecked")) {
            holder.mHourCheckBox.setChecked(false);
        } else if (state.equals("checked")) {
            holder.mHourCheckBox.setChecked(true);
        }

        //https://stackoverflow.com/questions/25646048/how-to-convert-local-time-to-am-pm-time-format-using-jodatime
        LocalTime time = new LocalTime(currentHour.getMilitaryHour(), 0);
        DateTimeFormatter fmt = DateTimeFormat.forPattern("h:mm a");
        holder.mHour.setText(fmt.print(time));

        holder.mHourCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton cb, boolean isChecked) {

                cb = (CheckBox) cb;

                if (cb.isChecked()) {
                    currentHour.setState(true);
                    mDb.child(protocolUserDateKey).child(String.valueOf(currentHour.getMilitaryHour())).setValue(currentHour);
                } else {
                    currentHour.setState(false);
                    mDb.child(protocolUserDateKey).child(String.valueOf(currentHour.getMilitaryHour())).setValue(currentHour);
                }
            }
        }); 
    }

    class HourHolder extends RecyclerView.ViewHolder {

        final CheckBox mHourCheckBox;
        final ImageView mOpenTasks;
        final TextView mHour;

        HourHolder(View view) {
            super(view);
            mHourCheckBox = (CheckBox) view.findViewById(R.id.cb_hour_all);
            mOpenTasks = (ImageView) view.findViewById(R.id.open_tasks);
            mHour = (TextView) view.findViewById(R.id.hour);
        }
    }

    private String determineCheckboxState(Hour hour) {
        boolean allFalse = true;
        boolean allTrue = true;

        // Check the state of Juice, Meal, and CE.
        if (hour.getJuice() != null && hour.getJuice().getState() == true ||
            hour.getMeal() != null && hour.getMeal().getState() == true ||
            hour.getCe() != null && hour.getCe().getState() == true) {
            allFalse = false;
        } else {
            allTrue = false;
        }

        // Check the state of the supplements.
        ArrayList<Supplement> supplements = hour.getSupplements();
        for (int i = 0; i < supplements.size(); i++) {
            if (supplements.get(i).getState() == true) {
                allFalse = false;
            } else {
                allTrue = false;
            }
        }

        if (allFalse == true) {
            return "unchecked";
        } else if (allTrue == true) {
            return "checked";
        }
    }
}

The ".child(String.valueOf(currentHour.getMilitaryHour()))" part should limit the update to only the appropriate hour, no? When I run through things with the debugger, the "currentHour" is always the correct info.

Update: This db update code is inside the Recyclerview FirebaseRecyclerAdapter's onBindViewHolder. And putting a Log.d inside onBindViewHolder logs a message multiple times whenever the random updates occurs. Which means onBindViewHolder is being called multiple times sometimes. But why?

ReyAnthonyRenacia
  • 17,219
  • 5
  • 37
  • 56
Michelle Williamson
  • 2,516
  • 4
  • 18
  • 19
  • Could you please post your RecyclerView code? It's looking like an issue with the way you handle your ViewHolders. – urgentx Sep 05 '18 at 01:16
  • I've added the full adapter code. Let me know if that's not what you meant. – Michelle Williamson Sep 05 '18 at 01:56
  • How about your determineCheckboxState() method? Could you post that too? – urgentx Sep 05 '18 at 02:26
  • I can but it's pretty irrelevant to the situation. I was using it with a third party "Indeterminent" checkbox but stripped out that functionality to eliminate it as the cause. I've also removed the function completely and the problem still stands. I'll add though. – Michelle Williamson Sep 05 '18 at 02:40
  • From what I've been able to gather since posting, the problem lies in using an OnCheckedChangeListener inside a recycler view because of the views getting recycled. Just not sure how to correct the situation yet. – Michelle Williamson Sep 05 '18 at 02:45
  • 1
    Aha! This seems to be doing the trick: https://stackoverflow.com/questions/17372750/android-setoncheckedchangelistener-calls-again-when-old-view-comes-back#answer-38435286 – Michelle Williamson Sep 05 '18 at 02:56

0 Answers0