Can onCheckedChanged method be called with no interaction?
Asked Answered
R

4

5

I have a custom ListView where clicking an item fires a new Activity. Each row of the ListView has a CheckBox and a TextView.

This is the getView method from my custom ListView:

public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        if (v == null) {
            LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.puzzles_row, null);
        }
        ListedPuzzle lp = items.get(position);
        if (lp != null) {
            TextView title = (TextView) v.findViewById(R.id.listTitles);
            title.setText(lp.getTitle());
            CheckBox star = (CheckBox) v.findViewById(R.id.star_listed);
            star.setChecked(lp.isStarred());
            star.setTag(new Integer(position));

            star.setOnCheckedChangeListener(new OnCheckedChangeListener() {

                public void onCheckedChanged(CompoundButton buttonView,
                        boolean isChecked) {
                    Integer realPosition = (Integer) buttonView.getTag();
                    ListedPuzzle obj = items.get(realPosition);
                    starListedPuzzle(obj.getId(), isChecked);
                }
            });

        }
        return v;

    }

The onCheckedChanged is called when coming back from the another Activty, without possible user interaction to actually check anything.

The point is that used to work fine and haven't really change anything. I've read that it's a bug but can't really believe it.

It always calls the method on the same two items of the ListView. Could they be set to checkedChange=true somehow?

Rissole answered 18/5, 2012 at 18:41 Comment(0)
A
11

It can be called on onRestoreInstanceState within your fragment

You may disable that with

android:saveEnabled="false"

on the Checkbox's layout definition

Alameda answered 10/6, 2015 at 18:44 Comment(1)
This is the best solution for a really annoying problem. If onCheckedChanged() is being called unexpectedly try this.Rockrose
Z
10

I have a feeling it is happening in the onRestoreInstanceState method. Android automatically handles restoring the state of most Views in your activity when it is paused and resumed. When being resumed, it sounds like it's restoring the checked state of your CheckBox objects, which is triggering onCheckedChanged. Try doing this in your Activity and let me know if this solves your problem:

private boolean mRestoringInstanceState;

@Override
protected void onRestoreInstanceState( Bundle savedInstanceState ) {

    mRestoringInstanceState = true;
    super.onRestoreInstanceState( savedInstanceState );
    mRestoringInstanceState = false;
}

Then change your onCheckChanged method like so:

public void onCheckedChanged(CompoundButton buttonView,
        boolean isChecked) {

    if(!mRestoringInstanceState) {
        Integer realPosition = (Integer) buttonView.getTag();
        ListedPuzzle obj = items.get(realPosition);
        starListedPuzzle(obj.getId(), isChecked);
    }
}

Edit: Probably a better/easier solution would be to assign the listener in the onResume method instead since it gets called after onRestoreInstanceState. Note that you wouldn't implement any of the above if using this solution:

@Override
protected void onResume() {

    super.onResume();
    CheckBox star = (CheckBox) v.findViewById(R.id.star_listed);
    star.setOnCheckedChangeListener(new OnCheckedChangeListener() {

        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
            // onCheckedChanged implementation
        }
    });
}

Edit2: Just realized you're doing this in an Adapter. This may not be related to onRestoreInstanceState, but I think I see the problem. You are reusing Views in the Adapter, which is good, but you're setting star to checked before setting the listener. The problem is that star already has a listener from the last time it came through the getView method. Before you call star.setChecked(), call star.setOnCheckedChangedListener(null) and see if that solves your problem.

Zahara answered 18/5, 2012 at 18:49 Comment(11)
You are absolutely right! Now, though, clicking on my CheckBox doesn't do anything ofcourse.Rissole
It should if you're setting mRestoringInstanceState back to false after super.onRestoreInstanceState finishes executing.Zahara
Additionally, you could also move the star.setOnCheckedChangeListener... line to the onResume method, as this gets called after onRestoreInstanceState. You wouldn't need to implement onRestoreInstanceState in this case. I'll edit my answer to show this option.Zahara
Well, it works like I said if I set if(mRestoringInstanceState), but it doesnt work if I set if(!mRestoringInstanceState)Rissole
@JasonRobinson: Wouldn't your onResume solution also require the listener to be set to null in onPause?Trouper
@MisterSquonk No, I don't believe listeners are restored via onRestoreInstanceState.Zahara
@Rissole I just realized you're doing this in an Adapter and not an Activity. Makes my solution not worthwhile for you. Let me give you another solution.Zahara
@Rissole Sorry I'm throwing you around to different answers, but check my 2nd edit.Zahara
@Jason Robinson thanks, don't worry. I was just realising your 2nd solution can't be achieved since the CheckBox hasn't been initialised yet.Rissole
@JasonRobinson, Working like a charm now! I'll keep dwelling on it for a while because didn't really get it yet. Thanks you so much.Rissole
@Rissole The reason it works like that is that since you are reusing views in getView, star already has a listener attached to it from the last time that view was passed through getView. So when you checked star, it trigger the last listener that was assigned to it. It's up to you to uninitialize this before checking it.Zahara
S
6

I ran into this issue as well using a custom Adapter.

I found checking if the button isShown solves the problem:

OnCheckedChangeListener ccl = new OnCheckedChangeListener()
{
    @Override
    public void onCheckedChanged(CompoundButton buttonView,
            boolean isChecked) {

        if(buttonView.isShown())
        {

        }
    }
};
Shutdown answered 20/3, 2014 at 16:28 Comment(1)
I think this is a fine solution as the system invoking the OnCheckedChangedListener when it is itself restoring state is obviously not what 99% of developers want. It is worth mentioning that ViewCompat.isLaidOut(buttonView) also works for this purpose and isn't tied strictly to visibility.Cuneiform
T
2

Check your stacktrace in the listener

public void onCheckedChanged(CompoundButton buttonView, isChecked) {
    <breakpoint here>
}

If you see onRestoreInstanceState() in the stacktrace you can use this in the layout:

<CheckBox
...
android:saveEnabled="false" />

... as suggested here: Android CheckBox -- Restoring State After Screen Rotation

or

If you see that star.setChecked() is the one called from you getView() in the stacktrace then just remove the listener by star.setOnCheckedChangedListener(null) before star.setChecked() (then you can reset the listener as you do). Like Jason Robinson suggests.

Thwart answered 13/3, 2015 at 18:41 Comment(1)
saved my day :) though I was not using it in adapter.Singleaction

© 2022 - 2024 — McMap. All rights reserved.