Skip to content

fix: Android back gesture navigates within embedded workflow paywalls - #1854

Draft
tonidero wants to merge 5 commits into
mainfrom
fix/android-workflow-back-gesture
Draft

fix: Android back gesture navigates within embedded workflow paywalls#1854
tonidero wants to merge 5 commits into
mainfrom
fix/android-workflow-back-gesture

Conversation

@tonidero

@tonidero tonidero commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

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. ReactActivity routes the back press to JS (react-navigation), which pops the screen, so the native paywall's Compose BackHandler (the only thing that handles workflow-internal back navigation) never runs.

Fix

Intercept hardwareBackPress in JS while the full-screen paywall is mounted (Android only) and forward it to the Activity's OnBackPressedDispatcher, letting the paywall's own BackHandler navigate the workflow or dismiss at the first step.

Only affects the embedded view; presentPaywall() (dedicated activity) was already correct. iOS unaffected.

vegaro and others added 5 commits July 14, 2026 16:58
… 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>
@tonidero tonidero added pr:fix A bug fix pr:other A code change that improves performance and removed pr:fix A bug fix labels Jul 17, 2026
if (Platform.OS !== "android" || !NativePaywall) {
return;
}
const subscription = BackHandler.addEventListener("hardwareBackPress", () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we will handle the back handling, not the RN engine.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it work if we return false here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +88 to +101
/**
* 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()
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this still work with non-workflow paywalls? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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...

Base automatically changed from feat/workflows-support to main July 20, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:other A code change that improves performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants