purchases-flutter icon indicating copy to clipboard operation
purchases-flutter copied to clipboard

Android back gesture doesn't pop PaywallView + no control over Navigator pop result

Open rignaneseleo opened this issue 7 months ago • 2 comments

I'm using PaywallView from the purchases_ui_flutter package inside a full-screen Scaffold on a Flutter page. Here's a minimal example of the setup:

class SoftPaywallPage extends StatelessWidget {
  final PurchaseService purchaseService;
  final SoftPaywallPageArguments arguments;

  SoftPaywallPage({
    super.key,
    required this.purchaseService,
    required this.arguments,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: MyAppBar(...),
      body: PaywallView(
        offering: purchaseService.fetchOffering(arguments.offering),
        displayCloseButton: true,
        onPurchaseCompleted: (customerInfo, transaction) {
          // Show success snackbar
        },
        onPurchaseError: (error) {
          // Show error snackbar
        },
        onRestoreCompleted: (customerInfo) async {
          // Check entitlements and show success/failure snackbar
        },
        onRestoreError: (error) {
          // Show restore error snackbar
        },
        onDismiss: () async {
          await purchaseService.refreshCustomerInfo();
          // Called when close button is tapped — but not when back gesture is used on Android
        },
      ),
    );
  }
}

Issues

  1. Back gesture on Android doesn't dismiss the page. The PaywallView does not respond to the system back gesture or hardware back button. It feels like the gesture is being intercepted and the route doesn't pop.

  2. Cannot return values from Navigator.pop() after purchases. I'd like to return a value with Navigator.pop(context, true) on successful purchase or restore, and false on failure. However, PaywallView doesn't provide a way to do this, which limits interaction with upstream navigation logic.

Expected behavior

  • The Android back gesture or hardware back button should properly dismiss the page.
  • PaywallView should expose a mechanism to pass a result on dismissal (especially from purchase/restore success/failure flows).

Flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.29.3, on macOS 15.3.2 24D81 darwin-arm64, locale it-IT)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.3)
[✓] VS Code (version 1.99.3)
[✓] Connected device (4 available)
[✓] Network resources

Package versions

purchases_flutter: ^8.8.0  
purchases_ui_flutter: ^8.8.0

Let me know if there’s a workaround. Thanks for your work on the SDK!

rignaneseleo avatar May 08 '25 09:05 rignaneseleo

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

RCGitBot avatar May 08 '25 09:05 RCGitBot

Hi @rignaneseleo! Thanks for sharing. I just tested the predictive back gesture on an Android device, and the paywall popped without an issue. Here's a short screen recording:

https://github.com/user-attachments/assets/12468ad6-8743-45bf-a50b-b1f9a9694249

For reference, the code for the paywall is here:

return Scaffold(
      body: PaywallView(
        offering: widget.offering,
        displayCloseButton: true,
        onPurchaseStarted: (Package rcPackage) {
          print('Purchase started for package: ${rcPackage.identifier}');
        },
        onPurchaseCompleted:
            (CustomerInfo customerInfo, StoreTransaction storeTransaction) {
          print('Purchase completed for customerInfo:\n $customerInfo\n '
              'and storeTransaction:\n $storeTransaction');
        },
        onPurchaseCancelled: () {
          print('Purchase cancelled');
        },
        onPurchaseError: (PurchasesError error) {
          print('Purchase error: $error');
        },
        onRestoreCompleted: (CustomerInfo customerInfo) {
          print('Restore completed for customerInfo:\n $customerInfo');
        },
        onRestoreError: (PurchasesError error) {
          print('Restore error: $error');
        },
        onDismiss: () {
          print('Paywall asked to dismiss');
          final data = <your_data>
          Navigator.pop(context, data);
        },
      ),
    );

You'll notice that this bit:

onDismiss: () {
          print('Paywall asked to dismiss');
          final data = <your_data>
          Navigator.pop(context, data);
        },

catches the onDismiss callback (which fired when I gestured back) and allows you to pass data back via pop, which you can then catch like so:

final result = await Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => PaywallScreen(
                            offering: offering,
                          ),
                        ),
                      );
                      print("Paywall result: $result");

Can you let me know if this isn't working on your test device or if I've misunderstood anything? I'm happy to help. Thanks!

Jethro87 avatar May 12 '25 20:05 Jethro87

Hey @rignaneseleo,

I was wondering if you were able to solve this issue per the comment above? Thanks!

mawr92 avatar Oct 13 '25 21:10 mawr92

Hey @rignaneseleo,

As we haven't heard back from you, I'll go ahead and close this issue. Please feel free to create a new one or contact support if you have any questions or would like to report an issue. 🚀

mawr92 avatar Oct 28 '25 20:10 mawr92