> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter Integration Patterns

> Two ways to handle Encore offer results in Flutter — async-result and handler-based — with a decision tree for picking the right one.

## Overview

`Encore.placement(id).show()` returns a `Future<EncorePresentationResult>`. You can either `await` it and switch on the result (async-result) or register global `setOnPurchaseRequest` / `setOnPassthrough` handlers (handler-based). Both work; pick whichever fits the call site.

For the cross-platform decision tree, see [Integration Patterns](/concepts/integration-patterns).

***

## Async-result pattern

Use this when the widget that triggers `show()` already has access to your purchase + decline logic.

### Basic example

```dart theme={null}
import 'package:encore_flutter/encore_flutter.dart';

Future<void> onCancelTapped(BuildContext context) async {
  final result = await Encore.shared.placement('cancel_flow').show();

  switch (result) {
    case EncorePresentationResultGranted(:final offerId):
      // User accepted
      Navigator.of(context).pushReplacementNamed('/home');
      break;
    case EncorePresentationResultNotGranted(:final reason):
      // User declined or no offers — proceed with original flow
      await proceedWithCancellation(reason);
      break;
  }

  // Code that runs in both branches lives here, written once
  analytics.track('cancel_flow_resolved');
}
```

### From a button

```dart theme={null}
FilledButton(
  onPressed: () async {
    final result = await Encore.shared.placement('cancel_flow').show();
    if (result is EncorePresentationResultGranted) {
      if (context.mounted) Navigator.of(context).pop();
    } else {
      await proceedWithCancellation();
    }
  },
  child: const Text('Cancel Subscription'),
)
```

### Result type

```dart theme={null}
sealed class EncorePresentationResult {}

class EncorePresentationResultGranted extends EncorePresentationResult {
  final String? offerId;
  final String? campaignId;
}

class EncorePresentationResultNotGranted extends EncorePresentationResult {
  final String reason;
}
```

***

## Handler pattern

Use this when `show()` is invoked from many sites that share post-purchase logic.

### Register at app init

```dart theme={null}
import 'package:encore_flutter/encore_flutter.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Encore.configure(apiKey: 'pk_live_...');

  Encore.shared.setOnPurchaseRequest((request) async {
    // Route to RevenueCat / in_app_purchase / your billing
    await billing.purchase(request.productId);
  });

  Encore.shared.setOnPassthrough((placementId) {
    AppRouter.handlePassthrough(placementId);
  });

  runApp(const MyApp());
}
```

### Then `show()` from anywhere

```dart theme={null}
ElevatedButton(
  onPressed: () => unawaited(Encore.placement('cancel_flow').show()),
  child: const Text('Cancel Subscription'),
)
```

`unawaited(...)` (from `dart:async`) is the idiomatic way to fire-and-forget when you don't need the result.

***

## When to use which

* **Async-result** — widget already imports your billing client. Branch-specific code lives at the call site.
* **Handler** — registered once at app init; many `show()` sites share the same purchase delegation.
* **Mixed** — handlers cover cross-cutting telemetry; specific call sites still `await show()`.

See the [decision tree](/concepts/integration-patterns#decision-tree) for the full rationale.

***

## Re-registration semantics

```dart theme={null}
Encore.shared.setOnPurchaseRequest((_) async { /* v1 */ });
Encore.shared.setOnPurchaseRequest((_) async { /* v2 — replaces v1 */ });
```

Each `setOn*` call replaces the previous callback. No double-fires.

***

## Platform-specific notes

* **`setOn*` naming** — Flutter uses `setOnPurchaseRequest`, `setOnPurchaseComplete`, `setOnPassthrough` (rather than the bare `onPurchaseRequest` setter on iOS/Android). Same semantics.
* **Native renders the UI** — the offer sheet renders natively (SwiftUI on iOS, Compose on Android). The Flutter layer is a thin bridge that triggers `show()` and receives results.
* **`unawaited(...)` for fire-and-forget** — use when the trigger is a synchronous callback and you don't want the analyzer warning about an unused `Future`.
* **Sealed-class switch** — Dart 3 sealed classes give you exhaustive `switch` over `EncorePresentationResultGranted` / `EncorePresentationResultNotGranted`. The compiler tells you when you've forgotten a branch.

***

## See also

* [Concepts — Integration Patterns](/concepts/integration-patterns) — cross-platform decision tree
* [Quickstart — Present Offers](/publishers/flutter/quickstart/present-offers) — handler-pattern walkthrough
