Skip to content

Commit

Permalink
Fix ReactSwitch for non RippleDrawable backgrounds (#32468)
Browse files Browse the repository at this point in the history
Summary:
ReactSwitch component is crashing on Android when it is initialised with both a backgroundColor and thumbColor, `style={{ backgroundColor: "anyColor" }} thumbColor="anyColor"`, due to IllegalCastException.

When setting a background color, BaseViewManagerDelegate is calling `setBackgroundColor` which replaces the background drawable with a ColorDrawale, hence [this line](https://github.com/facebook/react-native/blob/72ea0e111fccd99456abf3f974439432145585e3/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java#L68) fails.

Instead, given the ripple effect needs to be preserved, one should initialise a RippleDrawable using the current background drawable and set it as the background of the switch.

Given the RippleDrawable should be preserved, overriding the `setBackgroundColor` seemed the sensible thing to do.

## Changelog

[Android] [Fixed] - Fix crash when a Switch is initialised with both backgroundColor and thumbColor.

Pull Request resolved: #32468

Test Plan:
### Setup:
Initialise an empty React Native project. Add a switch component:
`<Switch
        style={{backgroundColor: 'red'}}
        thumbColor={'#356'}
      />`
Run the project `yarn android`

### Current state (RN 65+):
Red screen will show highlighting an IllegalCastException.

<img src="https://user-images.githubusercontent.com/4354327/138616661-3ba1370c-6a2b-48c2-ba70-b99415a4256f.png" width="200"/>

### With fix:
- The component is expected to have a red background.
- When pressed a ripple effect shows inside the backgrounds bounding box.
- Business as usual otherwise.

`backgroundColor` with `thumbColor`:
![backgroundColor + thumbColor](https://user-images.githubusercontent.com/4354327/138615603-141660d2-a5cd-49d7-aa5e-9c93ebc6d680.gif)

Just `thumbColor`:
![Screen Recording 2021-10-25 at 00 23 57](https://user-images.githubusercontent.com/4354327/138615658-baa380dd-2cbb-4d0f-a25e-a003ef67c977.gif)

Reviewed By: ShikaSD

Differential Revision: D31895690

Pulled By: cortinico

fbshipit-source-id: 60af16de7db61440ccfbf11d67a3d945dd90b562
  • Loading branch information
smarki authored and facebook-github-bot committed Oct 26, 2021
1 parent f58c496 commit 456cf3d
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
Expand Down Expand Up @@ -48,6 +49,18 @@ public void setChecked(boolean checked) {
}
}

@Override
public void setBackgroundColor(int color) {
// Ensure RippleDrawable is preserved for >=21
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setBackground(
new RippleDrawable(
createRippleDrawableColorStateList(color), new ColorDrawable(color), null));
} else {
super.setBackgroundColor(color);
}
}

void setColor(Drawable drawable, @Nullable Integer color) {
if (color == null) {
drawable.clearColorFilter();
Expand All @@ -63,14 +76,10 @@ public void setTrackColor(@Nullable Integer color) {
public void setThumbColor(@Nullable Integer color) {
setColor(super.getThumbDrawable(), color);

// Set the ripple color with thumb color if >= LOLLIPOP
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
RippleDrawable ripple = (RippleDrawable) super.getBackground();
ColorStateList customColorState =
new ColorStateList(
new int[][] {new int[] {android.R.attr.state_pressed}}, new int[] {color});

ripple.setColor(customColorState);
// Set the ripple color if background is instance of RippleDrawable
if (color != null && super.getBackground() instanceof RippleDrawable) {
ColorStateList customColorState = createRippleDrawableColorStateList(color);
((RippleDrawable) super.getBackground()).setColor(customColorState);
}
}

Expand Down Expand Up @@ -113,4 +122,9 @@ private void setTrackColor(boolean checked) {
setTrackColor(currentTrackColor);
}
}

private ColorStateList createRippleDrawableColorStateList(@Nullable Integer color) {
return new ColorStateList(
new int[][] {new int[] {android.R.attr.state_pressed}}, new int[] {color});
}
}
23 changes: 23 additions & 0 deletions packages/rn-tester/js/examples/Switch/SwitchExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,23 @@ class OnChangeExample extends React.Component<{...}, $FlowFixMeState> {
}
}

class ContainerBackgroundColorStyleExample extends React.Component<
{...},
$FlowFixMeState,
> {
render() {
return (
<View>
<Switch
style={{backgroundColor: 'blue'}}
thumbColor="white"
value={true}
/>
</View>
);
}
}

exports.title = 'Switch';
exports.documentationURL = 'https://reactnative.dev/docs/switch';
exports.category = 'UI';
Expand Down Expand Up @@ -291,6 +308,12 @@ exports.examples = [
return <OnChangeExample />;
},
},
{
title: "The container's background color can be set",
render(): React.Element<any> {
return <ContainerBackgroundColorStyleExample />;
},
},
];

if (Platform.OS === 'ios') {
Expand Down

0 comments on commit 456cf3d

Please sign in to comment.