fix: Android back gesture navigates within embedded workflow paywalls - #1854
fix: Android back gesture navigates within embedded workflow paywalls#1854tonidero wants to merge 5 commits into
Conversation
… from React Native Adds an internal-only dangerousSettings object to configure() (via the shared PurchasesConfiguration type), carrying useWorkflows. It is not part of the public, documented API. Both platforms build the native DangerousSettings from the flag in the RN bridge and pass it through hybrid-common's existing configure, so hybrid-common needs no native changes: - Android builds DangerousSettings.forWorkflows() when useWorkflows is set, otherwise the default. - iOS builds it in a small Swift helper (RNPurchasesDangerousSettingsFactory), since useWorkflows is an @_spi(Internal) flag not reachable from the Objective-C bridge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On Android, ReactActivity routes the back press to JS (react-navigation) instead of the Activity's OnBackPressedDispatcher, so the embedded <RevenueCatUI.Paywall> view never receives it and multipage workflow paywalls close entirely instead of navigating to the previous step. Intercept hardwareBackPress in JS and forward it to the native paywall's OnBackPressedDispatcher so its Compose BackHandler can navigate within the workflow (or dismiss when at the first step). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| if (Platform.OS !== "android" || !NativePaywall) { | ||
| return; | ||
| } | ||
| const subscription = BackHandler.addEventListener("hardwareBackPress", () => { |
There was a problem hiding this comment.
TBH, I'm wondering if this is a good approach... I believe the issue is partly related to our sample app using react navigation, but I think that's very common so feels like we need to fix this.
| } | ||
| const subscription = BackHandler.addEventListener("hardwareBackPress", () => { | ||
| RNPaywalls?.handlePaywallBackPress(); | ||
| return true; |
There was a problem hiding this comment.
This means we will handle the back handling, not the RN engine.
There was a problem hiding this comment.
I think if devs are calling BackHandler.addEventListener("hardwareBackPress", themselves it terminates the chain of calls and the dev's callbacks (and react-navigation's) never get a chance to run while the paywall is mounted.
https://reactnative.dev/docs/backhandler
If one subscription returns true, then subscriptions registered earlier will not be called.
There was a problem hiding this comment.
does it work if we return false here?
There was a problem hiding this comment.
Yeah this is part of the issue we have with this fix... For context, if we set this to false, it would go through both paths, and potentially result in unexpected behavior. For example, see this video, where you can see a flash of the workflow going back to the first screen but also closing the workflow entirely
Screen_recording_20260720_113542.mp4
| /** | ||
| * Forwards a back press to the Activity's OnBackPressedDispatcher so the embedded paywall's | ||
| * own Compose BackHandler can navigate within a workflow (or dismiss when at the first step). | ||
| * React Native routes back presses to JS instead of the dispatcher, so the embedded paywall | ||
| * never receives them unless we forward them explicitly. | ||
| */ | ||
| @ReactMethod | ||
| fun handlePaywallBackPress() { | ||
| val activity = currentFragmentActivity ?: return | ||
| activity.runOnUiThread { | ||
| activity.onBackPressedDispatcher.onBackPressed() | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Would this still work with non-workflow paywalls? 🤔
There was a problem hiding this comment.
It does, basically it just moves the back handling to the system, where our own native code picks it. The problem is that the RN navigation system handles the back gesture for us, so we don't really handle the back gesture without this... Maybe it's something that the developer can implement on their side as well though...
Problem
On Android, pressing back inside a multipage (workflow) paywall shown via the embedded
<RevenueCatUI.Paywall>view closes the whole paywall instead of navigating to the previous step.ReactActivityroutes the back press to JS (react-navigation), which pops the screen, so the native paywall's ComposeBackHandler(the only thing that handles workflow-internal back navigation) never runs.Fix
Intercept
hardwareBackPressin JS while the full-screen paywall is mounted (Android only) and forward it to the Activity'sOnBackPressedDispatcher, letting the paywall's ownBackHandlernavigate the workflow or dismiss at the first step.Only affects the embedded view;
presentPaywall()(dedicated activity) was already correct. iOS unaffected.