Alisa Petrova
Alisa Petrova

Reputation: 101

Error with checkbox interface and recyclerview adapter

I have a RecyclerView that displays tasks and contains checkboxes. When user will click the checkbox, I want to check if the time ending of the task is less than the current time and if it is true, then the checkbox will remain checked, otherwise it should be unchecked. Let me clarify my problem with the help of the code.

In my adapter I created an interface:

private OnItemClickedListener listener;

public void setOnItemClickedListener(OnItemClickedListener listener){
    this.listener = listener;
}
interface OnItemClickedListener {
    void onItemClick(View v, int position, boolean isChecked, int time);
}

Then, in OnBindViewHolder I set onClickListener to checkbox:

@Override
public void onBindViewHolder(@NonNull final SortedViewHolder holder, final int position) {
    final Sorted data = list.get(position);
    holder.title.setText(data.getSortedName());
    holder.date.setText(data.getSortedDate());
    holder.category.setText(String.valueOf(data.getSortedCategory()));
    holder.attach.setText(String.valueOf(data.isSortedAttach()));
    holder.to.setText(String.valueOf(toTime(data.getSortedDuration() + data.getSortedTimeBegin())));
    holder.from.setText(String.valueOf(toTime(data.getSortedTimeBegin())));
    holder.checkBox.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (listener != null){
                boolean isChecked = holder.checkBox.isChecked();
                listener.onItemClick(v, position, isChecked, data.getSortedDuration() + data.getSortedTimeBegin());
            }
        }
    });
}

(Note: I store time of the tasks in minutes, so later I will split them seperately into minutes and hours).

After that, in my activity I get this method and check the time:

//in OnCreate: 
final SortedAdapter adapter = new SortedAdapter();
adapter.setOnItemClickedListener(this);

@Override
public void onItemClick(View v, int position, boolean isChecked, int time) {
    if (isChecked){
        //String currentTime = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(new Date());
        //get current day time
        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        int minute = Calendar.getInstance().get(Calendar.MINUTE);
        //compare to the given
        if (hour > time/60){
            //save state
            Toast.makeText(this, "check1", Toast.LENGTH_SHORT).show();
        }
        else if (hour == time/60){
            if (minute > time % 60){
                //save state
                Toast.makeText(this, "check2", Toast.LENGTH_SHORT).show();
            }
            else{
                //set the checkbox to false
                Toast.makeText(this, "uncheck1", Toast.LENGTH_SHORT).show();
                listener.onCheckBoxOff(v);
            }
        }
        else{
            Toast.makeText(this, "uncheck2", Toast.LENGTH_SHORT).show();
            listener.onCheckBoxOff(v);
        }
    }
}

All the Toasts work fine. Now I want somehow to access my checkbox variable and change it's state. And this is my problem. I don't really understand how to do it. I've tried to make another interface in my activity:

 //outside activity class
 interface CheckBoxOff {
    void onCheckBoxOff(View v);
 }

//in activity class before onCreate
private CheckBoxOff listener;

void setCheckboxOffListener(CheckBoxOff listener){
    this.listener = listener;
}

So then I implemented it in my adapter:

@Override
public void onCheckBoxOff(View v) {
    SortedViewHolder holder = new SortedViewHolder(v);
    holder.checkBox.setChecked(false);
}

And in BindViewHolder I wrote(maybe here is the mistake?):

ShowSortedActivity activity = new ShowSortedActivity();
activity.setCheckboxOffListener(this);

After starting my app I got error:

java.lang.NullPointerException: Attempt to invoke interface method 'void com.example.tryalgorithm.ui.CheckBoxOff.onCheckBoxOff(android.view.View)' on a null object reference
    at com.example.tryalgorithm.ui.ShowSortedActivity.onItemClick(ShowSortedActivity.java:104)
    at com.example.tryalgorithm.ui.SortedAdapter$1.onClick(SortedAdapter.java:66)
    at android.view.View.performClick(View.java:6304)
    at android.widget.CompoundButton.performClick(CompoundButton.java:134)
    at android.view.View$PerformClick.run(View.java:24803)
    at android.os.Handler.handleCallback(Handler.java:794)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:176)
    at android.app.ActivityThread.main(ActivityThread.java:6635)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)

Could you please explain what am I doing wrong here? Maybe there is another way to set the checkbox to false, not with the help of interface or this way is fine? Thanks for any help.

Activity code:

public class ShowSortedActivity extends AppCompatActivity {

SortedViewModel viewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_show_sorted);

    final SortedAdapter adapter = new SortedAdapter();
    RecyclerView showSorted = findViewById(R.id.show_sorted);
    showSorted.setLayoutManager(new LinearLayoutManager(this));
    showSorted.setHasFixedSize(true);
    showSorted.setAdapter(adapter);

    getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close);
    setTitle(R.string.Sorted);

    Intent intent = getIntent();
    String currentDate = intent.getStringExtra("value");

    viewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication())).get(SortedViewModel.class);
    try {
        viewModel.getSortedWhereDateIs(currentDate).observe(this, new Observer<List<Sorted>>() {
            @Override
            public void onChanged(List<Sorted> sorteds) {
                adapter.setSortedData(sorteds);
            }
        });
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

@Override
public void onItemClick(View v, int position, boolean isChecked, int time) {
    if (isChecked){
        //get current day time
        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        int minute = Calendar.getInstance().get(Calendar.MINUTE);
        //compare to the given
        if (hour > time/60){
            //save state
            Toast.makeText(this, "check1", Toast.LENGTH_SHORT).show();
        }
        else if (hour == time/60){
            if (minute > time % 60){
                //save state
                Toast.makeText(this, "check2", Toast.LENGTH_SHORT).show();
            }
            else{
                //set the checkbox to false
                Toast.makeText(this, "uncheck1", Toast.LENGTH_SHORT).show();
            }
        }
        else{
            //set the checkbox to false
            Toast.makeText(this, "uncheck2", Toast.LENGTH_SHORT).show();
        }
    }
}
}

Upvotes: 0

Views: 71

Answers (1)

C&#244;ng Hải
C&#244;ng Hải

Reputation: 5241

@Override
public void onCheckBoxOff(View v) {
    SortedViewHolder holder = new SortedViewHolder(v);
    holder.checkBox.setChecked(false);
}

Your problem is you can not create view holder by self, it should be managed by adapter through onCreateViewHolder. Change your code to

@Override
public void onCheckBoxOff(View v) {
    ((CheckBox)v).setChecked(false);
}

But this only fix UI, you should store value of each holder to remain checkbox state when it scrolls. Following these steps:

  1. Add isChecked to Sorted model

  2. onBindViewHolder should update checkBox by isChecked from data

  3. inside method onItemClick should update isChecked model in list data of adapter base on position

Upvotes: 1

Related Questions