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

# EncorePresentationResult

> The factual record of a placement presentation: two independent funnels plus how the sheet ended

What [`show()`](./placement) resolves to. Either nothing ever appeared, or the interaction ran and the record carries **two independent funnels** plus how the sheet went away.

`show()` never throws on iOS or Android. Presentation-level failures arrive here as values.

## Structure

```dart theme={null}
sealed class EncorePresentationResult {
  // Fact readers: null when nothing was presented
  EncoreClaimedOffer? get claim;
  EncoreAdvertiserOutcome? get advertiser;
  EncorePublisherOutcome? get publisher;
  EncoreDismissReason? get dismissal;
}

final class EncoreNotPresented extends EncorePresentationResult {
  final EncoreNotPresentedReason reason;
  final String? errorType;     // set when reason is error
  final String? errorMessage;  // set when reason is error
}

final class EncorePresented extends EncorePresentationResult {
  final EncorePresentationOutcome outcome;
}

class EncorePresentationOutcome {
  final EncoreAdvertiserOutcome advertiser; // Encore's funnel: how far the claim got
  final EncorePublisherOutcome publisher;   // your funnel: what your controller reported
  final EncoreDismissReason dismissal;      // how the sheet went away
}
```

The two funnels are independent, not competing cases: a single presentation can claim an offer **and** run a purchase, and both are recorded. `notAttempted` is a real value, meaning "the funnel was open and nothing entered it", never a missing one.

## Reading it

The fact readers give you one axis at a time:

```dart theme={null}
final result = await Encore.placement('cancel_flow').show();

final converted = result.claim != null ||
    result.publisher == EncorePublisherOutcome.purchased;
converted ? grantAccess() : proceedWithCancellation();
```

`EncorePresentationResult` is sealed, so Dart 3 gives you an exhaustive `switch` when the mechanism matters:

```dart theme={null}
switch (result) {
  case EncoreNotPresented(:final reason):
    proceedWithCancellation(); // nothing was shown; reason says why
  case EncorePresented(:final outcome):
    analytics.log('encore_presentation', {
      'advertiser': outcome.advertiser.runtimeType.toString(),
      'publisher': outcome.publisher.nativeValue,
      'dismissal': outcome.dismissal.nativeValue,
    });
}
```

## EncorePublisherOutcome

Your funnel: what your [purchase controller](./purchase-controller) reported back.

| Value          | Wire value      | Meaning                                                                                                          |
| -------------- | --------------- | ---------------------------------------------------------------------------------------------------------------- |
| `notAttempted` | `not_attempted` | No purchase was attempted. Also what a presentation records when no purchase controller is registered            |
| `purchased`    | `purchased`     | Your controller returned `EncorePurchaseResult.purchased`                                                        |
| `cancelled`    | `cancelled`     | Your controller returned `EncorePurchaseResult.cancelled`                                                        |
| `pending`      | `pending`       | Your controller returned `EncorePurchaseResult.pending`: the store deferred the purchase and it may settle later |
| `failed`       | `failed`        | Your controller threw                                                                                            |
| `unknown`      | `unknown`       | A value this plugin version doesn't recognize, from a newer native SDK                                           |

Treat `pending` as neither a purchase nor an abandonment; see [EncorePurchaseController](./purchase-controller#definition) for what it means.

## EncoreAdvertiserOutcome

Encore's funnel: how far the offer claim got. A sealed class with five cases.

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

final class EncoreAdvertiserNotAttempted extends EncoreAdvertiserOutcome {}
final class EncoreAdvertiserClaimed extends EncoreAdvertiserOutcome {
  final EncoreClaimedOffer offer;
}
final class EncoreAdvertiserVerified extends EncoreAdvertiserOutcome {
  final EncoreClaimedOffer offer;
}
final class EncoreAdvertiserCooldown extends EncoreAdvertiserOutcome {}
final class EncoreAdvertiserFailed extends EncoreAdvertiserOutcome {
  final String? errorType;
  final String? errorMessage;
}
```

| Case                           | Meaning                                                                           |
| ------------------------------ | --------------------------------------------------------------------------------- |
| `EncoreAdvertiserNotAttempted` | The claim funnel was open and nothing entered it                                  |
| `EncoreAdvertiserClaimed`      | The claim completed on-device; server verification not (yet) observed             |
| `EncoreAdvertiserVerified`     | Server-level confirmation was observed in-session                                 |
| `EncoreAdvertiserCooldown`     | The claim was rejected server-side by the cross-offer provisional cooldown        |
| `EncoreAdvertiserFailed`       | The claim errored. An SDK-side failure the user never saw. See [Errors](./errors) |

`result.claim` is shorthand for the offer behind a `Claimed` or `Verified` outcome, and `null` otherwise. Read `advertiser` directly when you need to tell the two apart.

### EncoreClaimedOffer

```dart theme={null}
class EncoreClaimedOffer {
  final String? offerId;
  final String campaignId;
  final String advertiserName;
  final String? transactionId;
}
```

| Property         | Type      | Description                                                                                                                                                                                             |
| ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `offerId`        | `String?` | The claimed offer. Always `null` on Android, whose claim record carries only the campaign. On iOS an offer *is* a campaign, so this equals `campaignId`                                                 |
| `campaignId`     | `String`  | The campaign behind the offer                                                                                                                                                                           |
| `advertiserName` | `String`  | The brand behind the offer                                                                                                                                                                              |
| `transactionId`  | `String?` | Joins this claim to a later [`EncoreStrictUnlockVerified`](./outcomes) outcome and to the server-side completion event. Nullable, because the transaction write can fail while the claim still happened |

**`transactionId` is the one to keep.** The brand-side completion can land days later and reaches your server through the [offer-completed webhook](/publishers/offers-api/reference/offer-completed-webhook), which carries the same value. It is the only way to match this result to the completion it eventually produces.

## EncoreDismissReason

How a presented sheet went away. See [EncoreDismissReason](./dismiss-reason) for every value.

## EncoreNotPresentedReason

Why no sheet ever appeared.

| Value                  | Wire value             | Meaning                                                                                                | Platform |
| ---------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------ | -------- |
| `notConfigured`        | `not_configured`       | `show()` was called before `configure()`                                                               | Both     |
| `alreadyPresenting`    | `already_presenting`   | A presentation is already on screen                                                                    | Both     |
| `noOffers`             | `no_offer_available`   | No offers matched this user. Don't retry immediately                                                   | Both     |
| `experimentControl`    | `experiment_control`   | Control cohort: exposure logged, no UI by design                                                       | Both     |
| `useCaseUnavailable`   | `use_case_unavailable` | The requested use case resolved no layout for this app                                                 | Both     |
| `noForegroundActivity` | `no_foreground`        | No foreground Activity was available when the SDK needed one                                           | Android  |
| `unsupportedOS`        | `unsupported_ios`      | The device is below the SDK's minimum OS version (iOS 17)                                              | iOS      |
| `iapFirstDeclined`     | `iap_first_declined`   | An IAP-first layout ran its purchase before any UI and the user declined                               | Android  |
| `error`                | `error`                | Network, decoding, or integration failure. Read `errorType` and `errorMessage`. See [Errors](./errors) | Both     |
| `unknown`              | `unknown`              | A value this plugin version doesn't recognize, from a newer native SDK                                 | Both     |

<Note>
  `EncoreNotPresented` is not a synonym for failure. `noOffers`, `experimentControl`, and `useCaseUnavailable` are healthy states that still need your original flow to continue. Run your fallback for every reason, and log only `error`.
</Note>

The three enums decode an unrecognized wire value to their explicit `unknown` case. The advertiser funnel has no `unknown` case: an advertiser outcome this plugin version doesn't recognize reads as `EncoreAdvertiserNotAttempted`.

## Related

* [placement()](./placement): returns this record
* [EncorePurchaseController](./purchase-controller): whose answer becomes `publisher`
* [outcomes](./outcomes): carries this record for passive observers
* [Errors](./errors): the `errorType` values
