[bug/crash] MaskedView makes application crash on Android
Hi,
I'm experiencing an app crash on Android devices when using MaskedView. It occurs when leaving the screen (react navigation) with rendered MaskedView component and started happening after upgrading to RN 0.64.0.
Deps: "@react-native-masked-view/masked-view": "0.2.3", "react-native": "0.64.0",
P.s. iOS is working fine.
Logcat fragment:
021-04-13 12:31:49.611 15339-15339/[app] E/unknown:ReactNative: Exception thrown when executing ReactViewGroup.dispatchDraw method on ReactViewGroup[2999]
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setVisibility(int)' on a null object reference
at org.reactnative.maskedview.RNCMaskedView.updateBitmapMask(RNCMaskedView.java:81)
at org.reactnative.maskedview.RNCMaskedView.dispatchDraw(RNCMaskedView.java:35)
at android.view.View.buildDrawingCacheImpl(View.java:20689)
at android.view.View.buildDrawingCache(View.java:20555)
at android.view.View.draw(View.java:21145)
at android.view.ViewGroup.drawChild(ViewGroup.java:4388)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4173)
at com.facebook.react.views.view.ReactViewGroup.dispatchDraw(ReactViewGroup.java:710)
@DanielMarkiel
can you share example code to recreate this behavior?
Thanks
@k-ibr
Sure, here's the link to the repo (created with npx react-native init MyApp --template react-native-template-typescript) -> https://github.com/DanielMarkiel/reprex-masked-view-android-error/commit/0a198c020fa1ae030a474318dfc5476096ee9ba3
In short. The error occurs if we define custom headerLeft and we render MaskedView component on the screen which uses it, e.g.
<Stack.Screen
name="MaskedView"
component={MaskedViewScreen}
options={({navigation}) => ({
// Usage of custom 'headerLeft' breaks the app if MaskedView is rendered on the screen
headerLeft: () => (
<Button onPress={() => navigation.goBack()} title="Back" />
),
})}
/>
@DanielMarkiel I looked into this and it seems like MaskedView is trying to "modify" elements that no longer exist due to RN removing them from memory (by switching screens). So a quick fix for this is to check for null object on the view inside RNCMaskedView.java. You can try this by replacing the following functions by this code.
private void updateBitmapMask() {
if (this.mBitmapMask != null) {
this.mBitmapMask.recycle();
}
View maskView = getChildAt(0);
if (maskView != null) {
maskView.setVisibility(View.VISIBLE);
this.mBitmapMask = getBitmapFromView(maskView);
maskView.setVisibility(View.INVISIBLE);
} else{
this.mBitmapMask = null;
}
}
public void onDescendantInvalidated(View child, View target) {
super.onDescendantInvalidated(child, target);
if (!mBitmapMaskInvalidated) {
View maskView = getChildAt(0);
if (maskView != null) {
if (maskView.equals(child)) {
mBitmapMaskInvalidated = true;
}
}
}
}
this solves the crashing, but perhaps someone from the MaskedView team could look further into it because I am not 100% sure this is a clean solution. Could someone more experienced also look into this? @Naturalclar @FonDorn
@k-ibr i've created the same patch as you did. But the issue now is, that the mask is not applied during navigation back to previous screen 🤔
Don't know how to resolve this issue.
@friedolinfoerder thanks for also looking into it, def need someone more experienced with Android to have a look at this, at least we know whats going wrong. I’ll look deeper into it as well and will share my thoughts here.
But the issue now is, that the mask is not applied during navigation back to previous screen 🤔
@friedolinfoerder are you using react-native-screens? Probably this project needs same fix like here: https://github.com/react-native-svg/react-native-svg/pull/1542/files
Yes indeed, I use react-native-screens. @ku8ar Thanks for your help, but unfortunately the suggested fix does not change the wrong behavior (mask not applied during back navigation) 😕
I created PR. Thanks @k-ibr for this solution.
The fix has been released here: https://github.com/react-native-masked-view/masked-view/releases/tag/v0.2.5
that is great thanks a lot for the fix @khorark

RN 64 react-navigation 5 react-native-screens "@react-native-masked-view/masked-view": "^0.2.6",
then I press to current Bottom Tab to go to top stack, app crashed on android
my temporary decision
private void updateBitmapMask() { View maskView = getChildAt(0); if (maskView != null) { if (this.mBitmapMask != null) { this.mBitmapMask.recycle(); } maskView.setVisibility(View.VISIBLE); this.mBitmapMask = getBitmapFromView(maskView); maskView.setVisibility(View.INVISIBLE); } }
@k-ibr Thanks for the fix. I have another problem related to this issue Now when I go back from a screen, the MaskedView shows children for a short time (instead of masked view, or nothing)
https://user-images.githubusercontent.com/23259418/147493919-0971a1e1-1170-419b-94f8-d0ec2b44d101.mov
@k-ibr Thanks for the fix. I have another problem related to this issue Now when I go back from a screen, the MaskedView shows children for a short time (instead of masked view, or nothing)
Screen.Recording.1400-10-06.at.20.50.20.mov
Did you have solution for this? I have the same issue.
@highjump0615 @k-ibr Were either of you able to solve this? Thanks!
@highjump0615 @k-ibr Were either of you able to solve this? Thanks!
Hey Lucas,
No unfortunately haven't solved or to be honest continued investigating this any longer. We aren't having any of these issued within our project.
P.S. I also recommend looking at RN SKIA, it is a much nicer approach to masks.
goodluck!
@k-ibr Thanks for the fix. I have another problem related to this issue Now when I go back from a screen, the MaskedView shows children for a short time (instead of masked view, or nothing)
Screen.Recording.1400-10-06.at.20.50.20.mov
I had the same problem and solved it this way:
private View prevMaskView = null;
...
private void updateBitmapMask() {
if (this.mBitmapMask != null) {
this.mBitmapMask.recycle();
}
View maskView = getChildAt(0);
if (maskView != null) {
prevMaskView = maskView;
maskView.setVisibility(View.VISIBLE);
this.mBitmapMask = getBitmapFromView(maskView);
maskView.setVisibility(View.INVISIBLE);
} else {
prevMaskView.setVisibility(View.VISIBLE);
this.mBitmapMask = getBitmapFromView(prevMaskView);
prevMaskView.setVisibility(View.INVISIBLE);
}
}
should close issue if it's fixed