Skip to main content

Overview

Encore allows you to present targeted offers to users in exchange for rewards (free trials, discounts, credits) at critical moments like cancellation flows or feature paywalls. The native SDK renders all offer UI natively (SwiftUI on iOS, Jetpack Compose on Android) — the Flutter layer triggers presentation and receives results.

Present Offers

Presenting an Encore offer is a single call:
ElevatedButton(
  onPressed: () {
    Encore.shared.placement('cancellation_flow').show();
  },
  child: const Text('Cancel Subscription'),
)
Register handlers before presenting. See Handle Offer Results below.

Handle Offer Results

Register handlers that tell the SDK how to complete purchases and what to do when a user dismisses. Register these once after configure().

Register onPurchaseRequest

Called when a user accepts an offer and a purchase is needed. Use this to trigger a purchase via your Flutter-side subscription manager (RevenueCat, in_app_purchase, etc.).
Encore.shared.onPurchaseRequest((purchaseRequest) async {
  // Example using RevenueCat Flutter SDK
  await Purchases.purchaseProduct(purchaseRequest.productId);
});
If you do not set onPurchaseRequest, the native SDK handles the purchase itself using StoreKit (iOS) or Play Billing (Android) and presents the native subscription modal automatically. This is useful for apps that don’t use a third-party subscription manager.

Register onPassthrough

Called when the user dismisses the offer or no offers are available. Use this to resume the user’s original action (e.g., proceed with cancellation).
Encore.shared.onPassthrough((placementId) {
  // Resume your original user flow
  navigator.proceedWithCancellation();
});

onPurchaseComplete (Optional)

Only fires when no onPurchaseRequest handler is set and the native SDK handles the purchase itself. Use this to sync the transaction with subscription managers that don’t auto-detect native purchases.
Encore.shared.onPurchaseComplete((result, productId) {
  debugPrint('Native purchase completed: $productId');
});

Summary

CallbackWhen it firesRequired?
onPurchaseRequestUser accepts an offer, purchase neededRecommended
onPassthroughUser dismisses or no offers availableYes
onPurchaseCompleteNative purchase finishes (no onPurchaseRequest set)Optional
onPassthrough should be registered before presenting any placements so users are never blocked from completing their intended action.

Using the Result

placement().show() returns a PresentationResult that you can use for flow control:
final result = await Encore.shared.placement('cancel_flow').show();

switch (result) {
  case PresentationResultGranted(:final offerId):
    debugPrint('Offer granted: $offerId');
  case PresentationResultNotGranted(:final reason):
    debugPrint('Not granted: $reason');
}

Full Example

import 'package:flutter/material.dart';
import 'package:encore/encore.dart';

class CancelScreen extends StatelessWidget {
  const CancelScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Account')),
      body: Center(
        child: FilledButton(
          onPressed: () async {
            final result = await Encore.shared
                .placement('cancellation_flow')
                .show();

            if (result is PresentationResultGranted) {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('Offer applied!')),
              );
            }
          },
          child: const Text('Cancel Subscription'),
        ),
      ),
    );
  }
}

Next Steps