Skip to content

Commit

Permalink
[ChipGroup] Fix ChipGroup.getCheckedChipIds() returns wrong state
Browse files Browse the repository at this point in the history
In the Chip implementation, onCheckedChangeListener was called before onCheckedChangeListenerInternal. This causes an issue that in onCheckedChangeListener's callback, the checkable group's checked state is not updated yet, therefore ChipGroup.getCheckedChipIds() will return the outdated checked state.

Fixes this by overriding Chip.setOnCheckedChangeListener to get full control of the execution order between onCheckedChangeListener and onCheckedChangeListenerInternal.

Resolves #2691

PiperOrigin-RevId: 449100861
(cherry picked from commit 413a047)
  • Loading branch information
drchen authored and pekingme committed May 25, 2022
1 parent bef8ca1 commit 0356d7c
Show file tree
Hide file tree
Showing 55 changed files with 636 additions and 58 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
30 changes: 23 additions & 7 deletions lib/java/com/google/android/material/chip/Chip.java
Expand Up @@ -16,6 +16,7 @@

package com.google.android.material.chip;

import android.widget.CompoundButton.OnCheckedChangeListener;
import com.google.android.material.R;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
Expand Down Expand Up @@ -54,6 +55,7 @@
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.CompoundButton;
import androidx.annotation.AnimatorRes;
import androidx.annotation.BoolRes;
import androidx.annotation.CallSuper;
Expand Down Expand Up @@ -151,6 +153,7 @@ public class Chip extends AppCompatCheckBox
@Nullable private RippleDrawable ripple;

@Nullable private OnClickListener onCloseIconClickListener;
@Nullable private CompoundButton.OnCheckedChangeListener onCheckedChangeListener;
@Nullable private MaterialCheckable.OnCheckedChangeListener<Chip> onCheckedChangeListenerInternal;
private boolean deferredCheckedValue;
private boolean closeIconPressed;
Expand Down Expand Up @@ -251,6 +254,19 @@ public Chip(Context context, AttributeSet attrs, int defStyleAttr) {
setMinHeight(minTouchTargetSize);
}
lastLayoutDirection = ViewCompat.getLayoutDirection(this);

super.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (onCheckedChangeListenerInternal != null) {
onCheckedChangeListenerInternal.onCheckedChanged(Chip.this, isChecked);
}
if (onCheckedChangeListener != null) {
onCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
}
}
});
}

@Override
Expand Down Expand Up @@ -707,17 +723,17 @@ public void setChecked(boolean checked) {
// Defer the setChecked() call until after initialization.
deferredCheckedValue = checked;
} else if (chipDrawable.isCheckable()) {
boolean wasChecked = isChecked();
super.setChecked(checked);

if (wasChecked != checked) {
if (onCheckedChangeListenerInternal != null) {
onCheckedChangeListenerInternal.onCheckedChanged(this, checked);
}
}
}
}

@Override
public void setOnCheckedChangeListener(
@Nullable CompoundButton.OnCheckedChangeListener listener) {
// Do not call super here - the wrapped listener set in the constructor will call the listener.
onCheckedChangeListener = listener;
}

/** Register a callback to be invoked when the close icon is clicked. */
public void setOnCloseIconClickListener(OnClickListener listener) {
this.onCloseIconClickListener = listener;
Expand Down
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.android.material.appbar;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.common.truth.Truth.assertThat;

Expand Down
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.android.material.badge;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.common.truth.Truth.assertThat;

Expand Down
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.android.material.badge;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.common.truth.Truth.assertThat;

Expand Down
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.android.material.bottomnavigation;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.common.truth.Truth.assertThat;

Expand Down
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.android.material.bottomsheet;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.common.truth.Truth.assertThat;

Expand Down
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.android.material.button;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.common.truth.Truth.assertThat;

Expand Down
Expand Up @@ -16,7 +16,7 @@

package com.google.android.material.button;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static android.view.View.GONE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
Expand Down
Expand Up @@ -16,7 +16,7 @@

package com.google.android.material.checkbox;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.common.truth.Truth.assertThat;

Expand Down
Expand Up @@ -15,15 +15,17 @@
*/
package com.google.android.material.chip;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import android.content.Context;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.CompoundButton;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat;
Expand Down Expand Up @@ -196,6 +198,29 @@ public void onCheckedChanged(ChipGroup group, List<Integer> checkedIds) {
assertThat(checkedIds).contains(second.getId());
}

@Test
public void multipleSelection_chipListener() {
chipgroup.setSingleSelection(false);

Chip first = (Chip) chipgroup.getChildAt(0);
first.setOnCheckedChangeListener(this::onChipCheckedStateChanged);

Chip second = (Chip) chipgroup.getChildAt(1);
second.setOnCheckedChangeListener(this::onChipCheckedStateChanged);

first.performClick();
getInstrumentation().waitForIdleSync();

assertThat(checkedChangeCallCount).isEqualTo(1);
assertThat(checkedIds).containsExactly(first.getId());

second.performClick();
getInstrumentation().waitForIdleSync();

assertThat(checkedChangeCallCount).isEqualTo(2);
assertThat(checkedIds).containsExactly(first.getId(), second.getId());
}

@Test
public void multiSelection_withSelectionRequired_unSelectsIfTwo() {
chipgroup.setSingleSelection(false);
Expand Down Expand Up @@ -260,4 +285,22 @@ public void isNotSingleLine_initializesAccessibilityNodeInfo() {
assertEquals(1, itemInfo.getRowIndex());
assertTrue(itemInfo.isSelected());
}

@Test
public void getChipAccessibilityClassName_multipleChecked_buttonName() {
Chip chip = (Chip) chipgroup.getChildAt(0);
assertEquals("android.widget.Button", chip.getAccessibilityClassName().toString());
}

@Test
public void getChipAccessibilityClassName_singleChecked_radioButtonName() {
chipgroup.setSingleSelection(true);
Chip chip = (Chip) chipgroup.getChildAt(0);
assertEquals("android.widget.RadioButton", chip.getAccessibilityClassName().toString());
}

private void onChipCheckedStateChanged(CompoundButton chip, boolean checked) {
checkedChangeCallCount++;
checkedIds = chipgroup.getCheckedChipIds();
}
}
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.android.material.chip;

import com.google.android.material.R;
import com.google.android.material.test.R;

import static com.google.android.material.internal.ViewUtils.dpToPx;
import static com.google.common.truth.Truth.assertThat;
Expand Down

0 comments on commit 0356d7c

Please sign in to comment.