> ## Documentation Index
> Fetch the complete documentation index at: https://helium-promptless-document-entitlement-management-features.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Quickstart (Flutter)

> Integrate Helium into your Flutter App

## Background

Get set up with the Helium SDK for Flutter. Reach out over your Helium slack channel or email [founders@tryhelium.com](mailto:founders@tryhelium.com) for any questions.

## Installation

Run `flutter pub add helium_flutter` or manually add the helium\_flutter package to your pubspec.yaml:

```yaml
dependencies:
  helium_flutter: ^3.0.5
```

Then run:

```bash
flutter pub get
```

The minimum version of Flutter supported by this SDK is **3.24.0**.

<Tip>
  **Recommended -**  Make sure that Swift Package Manager support is enabled:
</Tip>

```bash
flutter upgrade
flutter config --enable-swift-package-manager
```

See this [Flutter documentation](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) for more details about Swift Package Manager.

<Info>
  You can still use Cocoapods for your dependencies if preferred. If you need to disable Swift Package Manager dependencies after having enabled it, refer to that same [Flutter documentation](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers#how-to-turn-off-swift-package-manager).
</Info>

### iOS Settings

Helium requires iOS 15+. If your app already has a minimum of iOS 15 or higher, you're all set. This can be specified in your `ios/Podfile` with:

```
platform :ios, '15.0'
```

If you still see errors related to minimum iOS version, consider updating to 15.0 or higher [directly in the Xcode project](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers#how-to-use-a-swift-package-manager-flutter-plugin-that-requires-a-higher-os-version).

## Initialize Helium

In your app's initialization code (typically in `main.dart` or your root widget), add the following to configure Helium and prepare your paywalls:

```dart
import 'package:helium_flutter/helium_flutter.dart';

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

  final heliumFlutter = HeliumFlutter();
  await heliumFlutter.initialize(
    apiKey: "<your-helium-api-key>",
  );

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

<ResponseField name="initialize" type="method">
  <Expandable title="properties">
    <ResponseField name="apiKey" type="string" required>
      You can create or retrieve your api key from [Account Settings](https://app.tryhelium.com/profile).
    </ResponseField>

    <ResponseField name="callbacks" type="HeliumCallbacks?">
      *(Optional)* Pass in a listener for paywall events.
    </ResponseField>

    <ResponseField name="purchaseDelegate" type="HeliumPurchaseDelegate?">
      *(Optional)* Delegate for handling your own purchase logic. If not provided, Helium will handle purchase logic for you.
    </ResponseField>

    <ResponseField name="fallbackPaywall" type="Widget?">
      *(Optional)* A widget to display if paywall fails to display. See Fallbacks section.
    </ResponseField>

    <ResponseField name="customUserId" type="String?">
      *(Optional)* If set, a custom user id to use instead of Helium's. We'll use this id when forwarding to third party analytics services, so this can be used for attribution (e.g. an amplitude user id, or a custom user id from your own analytics service)
    </ResponseField>

    <ResponseField name="customUserTraits" type="Map<String, dynamic>?">
      *(Optional)* Pass in custom user traits to be used for targeting, personalization, and dynamic paywall content.
    </ResponseField>

    <ResponseField name="revenueCatAppUserId" type="String?">
      *(Optional)* RevenueCat ONLY. Supply RevenueCat appUserID here (and initialize RevenueCat before Helium initialize).
      You can also set this value outside of initialize: `HeliumFlutter().setRevenueCatAppUserId(await Purchases.appUserID)`
    </ResponseField>

    <ResponseField name="fallbackBundleAssetPath" type="String?">
      *(Optional)* Path to fallback bundle. See Fallbacks section.
    </ResponseField>

    <ResponseField name="paywallLoadingConfig" type="HeliumPaywallLoadingConfig?">
      *(Optional)* Set loading budget in seconds and whether to even show a loading state. This can be done per trigger.

      ```dart
      final paywallLoadingConfig = HeliumPaywallLoadingConfig(
        loadingBudget: 5, // after 5 seconds, show fallback instead
        perTriggerLoadingConfig: {
          "onboarding": TriggerLoadingConfig(useLoadingState: false),
          "from_menu": TriggerLoadingConfig(useLoadingState: true, loadingBudget: 2),
        }
      );
      ```
    </ResponseField>
  </Expandable>
</ResponseField>

<Accordion title="Custom User ID and Custom User Traits">
  You can set a custom user ID and custom user traits with the `initialize` method or by calling:

  ```dart
  await heliumFlutter.overrideUserId(
    newUserId: "your-custom-user-id",
    traits: {
      "exampleTrait": "value",
      "userType": "premium"
    }
  );
  ```
</Accordion>

<Accordion title="Checking Download Status">
  After initialization, you can check the status of the paywalls download:

  ```dart
  String downloadStatus = await heliumFlutter.getDownloadStatus() ?? 'Unknown';
  ```

  The download status will be one of the following:

  * `notDownloadedYet`: The download has not been initiated.
  * `inProgress`: The download has not been initiated.
  * `downloadSuccess`: The download was successful.
  * `downloadFailure`: The download failed.

  You can also check if paywalls have been downloaded with `await heliumFlutter.paywallsLoaded()`
</Accordion>

## Helium Events

To handle various [paywall-related events](/sdk/helium-events), implement `HeliumCallbacks` and pass it to initialize:

```dart
abstract class HeliumCallbacks {
  Future<void> onPaywallEvent(HeliumPaywallEvent heliumPaywallEvent);
}
```

For example:

```dart
class LogCallbacks implements HeliumCallbacks {
  @override
  Future<void> onPaywallEvent(HeliumPaywallEvent heliumPaywallEvent) async {
    log('onPaywallEvent: ${heliumPaywallEvent.type} - trigger: ${heliumPaywallEvent.triggerName}');
  }
}
```

## Handling Purchases

By default, Helium will handle purchase logic for you. If you want a custom implementation, you'll want to implement `HeliumPurchaseDelegate` and pass that into `initialize`.

```dart
abstract class HeliumPurchaseDelegate {
  Future<HeliumPurchaseResult> makePurchase(String productId);

  Future<bool> restorePurchases();
}
```

#### Basic Implementation

Here's a basic implementation of `HeliumPurchaseDelegate`:

```dart
import 'package:helium_flutter/helium_flutter.dart';
import 'dart:developer';

class PaymentCallbacks implements HeliumCallbacks {
  @override
  Future<HeliumPurchaseResult> makePurchase(String productId) async {
    log('makePurchase: $productId');
    // Implement your purchase logic here
    return HeliumPurchaseResult(status: HeliumTransactionStatus.purchased);
  }

  @override
  Future<bool> restorePurchases() async {
    log('restorePurchases called');
    // Implement your restore logic here
    return true;
  }
}
```

#### RevenueCat Implementation Example

```dart
import 'package:helium_flutter/helium_flutter.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'dart:developer';

class RevenueCatCallbacks implements HeliumCallbacks {
  @override
  Future<HeliumPurchaseResult> makePurchase(String productId) async {
    try {
      log('RevenueCat making purchase: $productId');
      final offerings = await Purchases.getOfferings();

      Package? packageToPurchase;

      // Search all offerings for package
      for (var offering in offerings.all.values) {
        for (var package in offering.availablePackages) {
          if (package.storeProduct.identifier == productId) {
            packageToPurchase = package;
            break;
          }
        }
        if (packageToPurchase != null) break;
      }

      if (packageToPurchase == null) {
        return HeliumPurchaseResult(
          status: HeliumTransactionStatus.failed,
          error: 'Product not found in any offering: $productId',
        );
      }

      final customerInfo = await Purchases.purchasePackage(packageToPurchase);

      // Check if the purchase was successful by looking at entitlements
      if (customerInfo.entitlements.active.isNotEmpty) {
        return HeliumPurchaseResult(status: HeliumTransactionStatus.purchased);
      } else {
        return HeliumPurchaseResult(status: HeliumTransactionStatus.failed);
      }
    } catch (e) {
      log('RevenueCat purchase error: $e');
      if (e is PurchasesErrorCode) {
        if (e == PurchasesErrorCode.purchaseCancelledError) {
          return HeliumPurchaseResult(status: HeliumTransactionStatus.cancelled);
        } else if (e == PurchasesErrorCode.paymentPendingError) {
          return HeliumPurchaseResult(status: HeliumTransactionStatus.pending);
        }
      }
      return HeliumPurchaseResult(
          status: HeliumTransactionStatus.failed,
          error: 'RevenueCat purchase error: ${(e as PlatformException?)?.message ?? "Unknown error"}'
      );
    }
  }

  @override
  Future<bool> restorePurchases() async {
    try {
      log('RevenueCat restoring purchases');
      final restoredInfo = await Purchases.restorePurchases();
      return restoredInfo.entitlements.active.isNotEmpty;
    } catch (e) {
      log('RevenueCat restore error: $e');
      return false;
    }
  }
}
```

## Presenting Paywalls

There are two ways to show a Helium paywall in your Flutter app:

### Via Direct Method Call (Recommended)

You can present a paywall programmatically using the `presentUpsell` method:

```dart
ElevatedButton(
  onPressed: () {
    final heliumFlutter = HeliumFlutter();
    heliumFlutter.presentUpsell(context: context, trigger: 'insert-trigger-here');
  },
  child: Text('Show Premium Features'),
),
```

Configure triggers in the dashboard. You can also pass in custom paywall traits and event handlers directly to `presentUpsell`:

```dart
HeliumFlutter().presentUpsell(
  trigger: 'my_paywall',
  context: context,
  eventHandlers: PaywallEventHandlers(
    onOpen: (event) {
      log('${event.type} - trigger: ${event.triggerName}');
    },
    onClose: (event) {
      log('${event.type} - trigger: ${event.triggerName}');
    },
    onDismissed: (event) {
      log('${event.type} - trigger: ${event.triggerName}');
    },
    onPurchaseSucceeded: (event) {
      log('${event.type} - trigger: ${event.triggerName}');
    },
  ),
  customPaywallTraits: {
    "has_seen_intro_video": true,
  }
);
```

### Via Widget Integration

You can also use the `HeliumFlutter.getUpsellWidget` method to embed a paywall directly in your widget tree:

```dart
class ExamplePageWithEmbeddedPaywall extends StatelessWidget {
  const ExamplePageWithEmbeddedPaywall({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: HeliumFlutter().getUpsellWidget(trigger: 'insert-trigger-here'),
    );
  }
}
```

<Note>
  You will have to handle your own dismissal in this case. You can do so by listening for the `paywallDismissed` event in `onPaywallEvent` of your `HeliumCallbacks` implementation.
</Note>

### Hiding Paywalls

To programmatically hide a paywall that was shown with the `presentUpsell` method:

```dart
bool hideResult = await heliumFlutter.hideUpsell() ?? false;
```

## Fallbacks

The Flutter SDK currently has two fallback options - 1) [Fallback bundle](/guides/fallback-bundle) and 2) a fallback view. Fallback situations should be quite rare, but to be safe, it is recommended you implement a fallback bundle or custom fallback view.
