react-native-purchases
react-native-purchases copied to clipboard
Android crash when unmounting PaywallFooterContainerView: java.lang.IllegalStateException: No ViewModelStoreOwner was provided via LocalViewModelStoreOwner
- [X] I have updated Purchases SDK to the latest version
- [X] I have read the Contribution Guidelines
- [X] I have searched the Community
- [X] I have read docs.revenuecat.com
- [X] I have searched for existing Github issues
Describe the bug
When unmounting the RevenueCatUI.PaywallFooterContainerView on Android devices, java.lang.IllegalStateException: No ViewModelStoreOwner was provided via LocalViewModelStoreOwner
is thrown.
- Environment
- Platform: Android only
- SDK version: 7.27.1 (both react-native-purchases and react-native-purchases-ui)
- OS version: Clip is on Android 10, but bug is on all versions.
- Xcode/Android Studio version: idk
- React Native version: 0.73.6
- SDK installation (CocoaPods + version or manual): expo
- How widespread is the issue. Percentage of devices affected: 100%
- Debug logs that reproduce the issue:
ERROR Your app just crashed. See the error below.
java.lang.IllegalStateException: No ViewModelStoreOwner was provided via LocalViewModelStoreOwner
at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt.getPaywallViewModel(InternalPaywall.kt:301)
at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt.InternalPaywall(InternalPaywall.kt:58)
at com.revenuecat.purchases.ui.revenuecatui.PaywallKt.Paywall(Paywall.kt:12)
at com.revenuecat.purchases.ui.revenuecatui.PaywallFooterKt$PaywallFooter$paywallComposable$1.invoke(PaywallFooter.kt:36)
at com.revenuecat.purchases.ui.revenuecatui.PaywallFooterKt$PaywallFooter$paywallComposable$1.invoke(PaywallFooter.kt:35)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at com.revenuecat.purchases.ui.revenuecatui.PaywallFooterKt.PaywallFooter(PaywallFooter.kt:45)
at com.revenuecat.purchases.ui.revenuecatui.views.PaywallFooterView$init$2$1.invoke(PaywallFooterView.kt:132)
at com.revenuecat.purchases.ui.revenuecatui.views.PaywallFooterView$init$2$1.invoke(PaywallFooterView.kt:128)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:428)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:252)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:251)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:195)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:119)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:118)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:110)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:158)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:157)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:157)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:142)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:78)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3340)
at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3273)
at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:588)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1013)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:520)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:142)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:133)
at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1191)
at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:133)
at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:183)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.kt:314)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.kt:192)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:140)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:133)
at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1191)
at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:133)
at androidx.compose.ui.platform.Wrapper_androidKt.doSetContent(Wrapper.android.kt:104)
at androidx.compose.ui.platform.Wrapper_androidKt.setContent(Wrapper.android.kt:83)
at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
at androidx.compose.ui.platform.AbstractComposeView.onMeasure(ComposeView.android.kt:288)
at android.view.View.measure(View.java:24723)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6903)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at com.revenuecat.purchases.react.ui.PaywallFooterViewManager$createViewInstance$1.onMeasure(PaywallFooterViewManager.kt:40)
at android.view.View.measure(View.java:24723)
at com.revenuecat.purchases.react.ui.PaywallFooterViewManager$createViewInstance$1.measureAndLayout$lambda$0(PaywallFooterViewManager.kt:28)
at com.revenuecat.purchases.react.ui.PaywallFooterViewManager$createViewInstance$1.$r8$lambda$ov1FIeRAm44dBT08dD92Nth7RB0
at com.revenuecat.purchases.react.ui.PaywallFooterViewManager$createViewInstance$1$$ExternalSyntheticLambda1.run
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7386)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:980)
- Steps to reproduce, with a description of expected vs. actual behavior:
- Create this component:
import {useState} from 'react';
import {Text, Switch} from 'react-native';
import RevenueCatUI from 'react-native-purchases-ui';
export default function PaywallExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Switch onChange={() => setIsOpen(!isOpen)} value={isOpen} />
{isOpen ? (
<>
<RevenueCatUI.PaywallFooterContainerView>
<Text>Footer Content</Text>
</RevenueCatUI.PaywallFooterContainerView>
</>
) : null}
</>
)
}
- Open paywall with switch
- Close paywall with switch
Expected behaviour: Paywall footer will be shown and then hidden without any issues Actual behaviour: Paywall footer is shown succesfully but crashes when unmounted.
Here is a video demonstration of the expected behaviour (on iOS) vs the actual behaviour (on Android) with the exact same code:
https://github.com/RevenueCat/react-native-purchases/assets/39602818/a176cf4e-0823-4b5a-9e87-91889e74b354
👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!
Hey @carter-0 !
Thanks for reaching out, this error usually happens when LocalViewModelStoreOwner.current is set to null. Can you please verify that this doesn't get set to null, it could be because of something like a rerender being triggered by your code. This could be the result of something in your switch case causing an issue or the state not being re-rendered properly.
Let me know if that helps!
@michaelAtRC Thanks for the response but I can't figure out how to check this. Is LocalViewModelStoreOwner not a Kotlin native component handled by react-native-purchases-ui? or is there a way to access this from react native?
Sorry for the ignorance, I'm pretty new to react native & app development😅
I'm experiencing this crash as well. Dismissing the paywall footer on Android causes the app to crash every time
For what it's worth, I'm not using any custom UI with my footer. I'm simply displaying it inside a BottomSheet. @carter-0 I was able to stop this crash from happening by commenting out this line:
https://github.com/RevenueCat/react-native-purchases/blob/3299266209c93e9cdd1e560bf22fd7c71e4a1d40/react-native-purchases-ui/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallFooterViewManager.kt#L24
This is fine for my use-case because my view isn't ever updating since I'm not using custom UI with my footer, which is the purpose of that call. The crash is 100% happening somewhere inside the onMeasure
call within PaywallFooterViewManager.kt
but I don't have time to track down the cause right now.
I think I'll just have to patch the library to comment that line to get my app working properly.
Thanks @Watersdr, I can confirm commenting that line out also fixes the crash for me, even with custom ui in the footer. I haven't noticed any other effects so far so I'm also going to patch the lib as a temporary fix.
Hey There,
Glad to see that this was fixed and things are now working as planned. On a side note, we have updated the SDK which has had more bug improvements since this, so I recommend updating to the latest version to ensure you have all the fixes that we have put out since this.
Hey There,
I switched to 7.28.1, the issue still persists, I had to change the paywallfootermanager.kt to fix the crash, is it fixed in version 8.0? Because it is a breaking change I don't want to switch for now.
The issue persist on 8.2.0 as well.
Still same +
Issue is still present in 8.2.1. Patching the PaywallFooterViewManager.kt (as described in https://github.com/RevenueCat/react-native-purchases/issues/994#issuecomment-2110607876) does fix the crash. So far haven't noticed any side-effects for my use case, however I am pretty sure that in some cases this is not a good way to fix this.
Hey There,
Glad to see that this was fixed and things are now working as planned. On a side note, we have updated the SDK which has had more bug improvements since this, so I recommend updating to the latest version to ensure you have all the fixes that we have put out since this.
Hey there the issue still persist, any official fix planning for this?
@michaelAtRC Hi Michael, As you can see to issue still persists and we are applying patching to the source code, any chance that it will be picked up and reopened?
Are there any updates on this?
Is there any update about this issue @michaelAtRC ?