> ## Documentation Index
> Fetch the complete documentation index at: https://helium-mintlify-create-navigation-structure-42614.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Quickstart (Android)

> Integrate Helium into your Android app

## Background

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

## Installation

<Tip>
  Version **4.x** of the Android SDK just released. To migrate from v0, view [the migration guide](/migrations/android-0-to-4). You can also view the [v0 guide here](/sdk/quickstart-android-v0).
</Tip>

Add the Helium SDK to your project using Gradle.

### Requirements

* **Kotlin Version**: 2.0.0 or higher
* **Java Version**: 8 or higher
* **Minimum Android SDK**: 23 or higher
* **Compile Android SDK**: 35 or higher

#### 1. Add repositories to your `settings.gradle.kts` file:

Ensure you have `mavenCentral()` and `google()` in your repositories blocks.

```kotlin theme={null}
// settings.gradle.kts

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // You might have other repositories here
    }
}
```

<Note>
  If you don't have a `dependencyResolutionManagement` block, ensure `google()` and `mavenCentral()` are present in your `pluginManagement { repositories { ... } }` block.
</Note>

#### 2. Add the dependencies to your module-level `build.gradle.kts` file (e.g., `app/build.gradle.kts`):

<Tabs>
  <Tab title="Core">
    ```kotlin theme={null}
    // app/build.gradle.kts

    dependencies {
        implementation("com.tryhelium.paywall:core:4.0.0")
    }
    ```
  </Tab>

  <Tab title="Jetpack Compose UI">
    ```kotlin theme={null}
    // app/build.gradle.kts

    dependencies {
        // For advanced cases - only include this if you want to display your paywall
        // as an embedded view.
        implementation("com.tryhelium.paywall:compose-ui:4.0.0")
    }
    ```
  </Tab>

  <Tab title="RevenueCat">
    ```kotlin theme={null}
    // app/build.gradle.kts

    dependencies {
        // If you're using RevenueCat, add this dependency:
        implementation("com.tryhelium.paywall:revenue-cat:4.0.0")
    }
    ```
  </Tab>
</Tabs>

## Initialize Helium

<Tip>
  Find your API key [here](https://app.tryhelium.com/profile)
</Tip>

Initialize the Helium SDK as early as possible in your app's lifecycle.

```kotlin theme={null}
Helium.initialize(
    context = this,
    apiKey = "YOUR_API_KEY",
    environment: HeliumEnvironment.PRODUCTION,
)
```

<Note>
  The supplied environment can be SANDBOX or PRODUCTION. If not sure, use PRODUCTION because Helium will automatically treat debug builds as SANDBOX.
</Note>

Choose the appropriate location based on your app's architecture:

<Tabs>
  <Tab title="Application">
    ```kotlin theme={null}
    class MyApplication : Application() {
        override fun onCreate() {
            super.onCreate()
            // Add this:
            configureHelium()
        }

        // And this:
        private fun configureHelium() {
            // Identify user and adjust Helium.config if needed (see next sections).
            // Then call initialize:
            Helium.initialize(
                context = this,
                apiKey = "YOUR_API_KEY",
                environment = HeliumEnvironment.PRODUCTION,
            )
        }
    }
    ```
  </Tab>

  <Tab title="Activity">
    ```kotlin theme={null}
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // Add this:
            configureHelium()
        }

        // And this:
        private fun configureHelium() {
            // Identify user and adjust Helium.config if needed (see next sections).
            // Then call initialize:
            Helium.initialize(
                context = this,
                apiKey = "YOUR_API_KEY",
                environment = HeliumEnvironment.PRODUCTION,
            )
        }
    }
    ```
  </Tab>
</Tabs>

And add necessary imports:

```kotlin theme={null}
import com.tryhelium.paywall.core.Helium
```

<Note>
  Helium's initialization runs on a background thread, so you don't have to worry about it affecting your app's launch time.
</Note>

## Identifying Users

Identifying users is optional but can help with targeting and when forwarding events to external analytics platforms.
If you are not sure, you probably do not need to identify your users.

<Tip>
  Identify users as early as you can to maximize consistency in metrics and targeting. Ideally right before you call `Helium.initialize`!
</Tip>

Set a custom user ID:

```kotlin theme={null}
Helium.identity.userId = "custom-user-id"
```

Set custom user traits for targeting and analytics visibility:

```kotlin theme={null}
Helium.identity.setUserTraits(HeliumUserTraits(
    traits = mapOf(
        "hasOnboarded" to HeliumUserTraitsArgument.BoolParam(true),
        "accountAge" to HeliumUserTraitsArgument.IntParam(30)
    )
))
// or Helium.identity.addUserTraits() if you don't want to clear existing traits
```

## Presenting Paywalls

<Note>
  You must have a trigger and workflow configured in the [dashboard](https://app.tryhelium.com/workflows) in order to show a paywall.
</Note>

Call `presentPaywall` when you want to show a full-screen paywall. For example:

```kotlin theme={null}
Helium.presentPaywall(
    trigger = "premium",
    onPaywallNotShown = { reason ->
        when (reason) {
            is PaywallNotShownReason.TargetingHoldout -> {
                // User is in holdout group
            }
            is PaywallNotShownReason.AlreadyEntitled -> {
                // User already has access
                // In order for this case to be hit, `config.dontShowIfAlreadyEntitled` must be true
            }
            is PaywallNotShownReason.Error -> {
                // Handle the rare case where a paywall
                // fails to show (see Fallbacks section)
            }
        }
    }
)
```

<ResponseField name="Helium.presentPaywall" type="method">
  <Expandable title="parameters">
    <ResponseField name="trigger" type="String" required>
      The trigger name configured in the Helium dashboard.
    </ResponseField>

    <ResponseField name="config" type="PaywallPresentationConfig?">
      *(Optional)* Configuration for this paywall presentation.

      ```kotlin theme={null}
      data class PaywallPresentationConfig(
          // Activity to present from. SDK auto-tracks if not provided.
          val fromActivityContext: Activity? = null,
          // Custom traits to send to the paywall
          val customPaywallTraits: HeliumUserTraits? = null,
          // Don't show paywall if user is entitled to a product in paywall
          val dontShowIfAlreadyEntitled: Boolean = false,
          // Disable system back button closing the paywall
          val disableSystemBackNavigation: Boolean = false,
          // How the paywall animates in
          val presentationStyle: HeliumPresentationStyle = HeliumPresentationStyle.SLIDE_UP,
          // Show in fullscreen "immersive" mode which will hide system status bars.
          val fullscreen: Boolean = false
      )
      ```
    </ResponseField>

    <ResponseField name="onEntitled" type="(() -> Unit)?">
      *(Optional)* A handler for when user is entitled to a product in the paywall, via purchase or existing entitlement.
    </ResponseField>

    <ResponseField name="eventListener" type="HeliumEventListener?">
      *(Optional)* Event listener for paywall lifecycle events.
    </ResponseField>

    <ResponseField name="onPaywallNotShown" type="((PaywallNotShownReason) -> Unit)?">
      *(Optional but highly recommended)* Handle any scenario where the paywall does not show. If user is already entitled and `config.dontShowIfAlreadyEntitled` is true, `onPaywallNotShown(AlreadyEntitled)` will be called only if `onEntitled` is not provided.
    </ResponseField>
  </Expandable>
</ResponseField>

You should now be able to see Helium paywalls in your app! Well done! 🎉

<Info>
  Looking for alternative presentation methods? Check out the guide on [Ways to Show a Paywall](/guides/ways-to-show-paywall).
</Info>

## PaywallEventHandlers

The Helium SDK allows you to listen for various paywall-related [events](/sdk/helium-events). This is useful for tracking analytics, responding to user interactions, or handling the paywall lifecycle.

There are two ways to listen for events: using the `PaywallEventHandlers` class for specific callbacks, or implementing the `HeliumEventListener` interface to receive all events.

### Option 1: Using PaywallEventHandlers

You can create an instance of `PaywallEventHandlers` and provide lambdas for the events you are interested in.

The available handlers are:

* `onOpen`: Called when a paywall is displayed to the user.
* `onClose`: Called when a paywall is closed for any reason.
* `onDismissed`: Called when the user explicitly dismisses a paywall without purchasing.
* `onPurchaseSucceeded`: Called when a purchase completes successfully.
* `onCustomPaywallAction`: Called when a custom action is triggered from the paywall.
* `onAnyEvent`: Called for any of the above events.

To register your handlers, use `Helium.shared.addPaywallEventListener`. You can either tie the listener to a lifecycle (recommended) or manage it manually.

**Lifecycle-Aware (Recommended)** Pass a `LifecycleOwner` (like an `Activity` or `Fragment`) to have the listener automatically removed when the lifecycle is destroyed.

```kotlin theme={null}
class MyActivity : AppCompatActivity() {
    private val paywallEventHandlers = PaywallEventHandlers(
        onOpen = { event -> print("Paywall opened: ${event.paywallName}") },
        onClose = { event -> print("Paywall closed: ${event.paywallName}") }
        // ... other event handlers
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Add the listener once, it will be cleaned up automatically
        Helium.shared.addPaywallEventListener(this, paywallEventHandlers)
    }
}
```

<Warning>
  If you don't provide a `LifecycleOwner`, you are responsible for removing the listener with `removeHeliumEventListener()` to prevent memory leaks.
</Warning>

### Option 2: Implementing HeliumEventListener

For a more centralized approach, your class can implement the `HeliumEventListener` interface and handle all events in a single `onHeliumEvent` method.

```kotlin theme={null}
import com.tryhelium.paywall.core.event.*

class MyActivity : AppCompatActivity(), HeliumEventListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Add the listener once, it will be cleaned up automatically
        Helium.shared.addPaywallEventListener(this, this)
    }
    override fun onHeliumEvent(event: HeliumEvent) {
        when (event) {
            is PaywallOpen -> {
                // Handle paywall open
            }
            is PaywallClose -> {
                // Handle paywall close
            }
            // ... handle other event types
        }
    }
}
```

## Purchase Handling

<Tip>
  By default, Helium will handle purchases for you! The SDK automatically creates a `PlayStorePaywallDelegate` if you don't provide one. This section is for those who want to use RevenueCat or implement custom purchase logic.
</Tip>

Use one of our pre-built `HeliumPaywallDelegate` implementations or create a custom delegate. Set the delegate before calling `initialize()`.

<Tabs>
  <Tab title="PlayStorePaywallDelegate">
    The PlayStorePaywallDelegate handles purchases using Google Play Billing:

    ```kotlin theme={null}
    // Optional - SDK auto-creates this if heliumPaywallDelegate is not set
    Helium.config.heliumPaywallDelegate = PlayStorePaywallDelegate(this)
    ```

    <Note>
      Want to add some custom behavior but still use the built-in purchase logic? Just subclass `PlayStorePaywallDelegate` or `RevenueCatPaywallDelegate`! (Be sure to make a `super` call for any overridden methods.)
    </Note>
  </Tab>

  <Tab title="RevenueCatPaywallDelegate">
    <Warning>
      Make sure you added the `revenue-cat` dependency as shown in the installation section.
    </Warning>

    Use RevenueCatPaywallDelegate to handle purchases through RevenueCat:

    ```kotlin theme={null}
    Helium.config.heliumPaywallDelegate = RevenueCatPaywallDelegate()
    ```

    <Note>
      Make sure to initialize RevenueCat *before* creating the RevenueCatPaywallDelegate!
    </Note>
  </Tab>

  <Tab title="Custom Delegate">
    You can also create a custom delegate and implement your own purchase logic.

    The `HeliumPaywallDelegate` is defined as follows:

    ```kotlin theme={null}
    interface HeliumPaywallDelegate {
        suspend fun makePurchase(
            productDetails: ProductDetails,
            basePlanId: String?,
            offerId: String?,
        ): HeliumPaywallTransactionStatus
        suspend fun restorePurchases(): Boolean
        fun onHeliumEvent(event: HeliumEvent)
    }
    ```

    The `makePurchase` function is called by the Helium SDK when a user initiates a purchase. It provides you with the following parameters:

    * `productDetails`: The `ProductDetails` object from the billing library for the selected product.
    * `basePlanId`: The identifier of the base plan, if applicable (for subscriptions).
    * `offerId`: The identifier of the offer, if applicable (for subscriptions with special offers).

    `HeliumPaywallTransactionStatus` is an enum that defines the possible states of a paywall transaction:

    ```kotlin theme={null}
    enum class HeliumPaywallTransactionStatus {
        PURCHASED,
        CANCELLED,
        FAILED,
        RESTORED,
        PENDING
    }
    ```
  </Tab>
</Tabs>

### Consumables

<Note>
  This only applies if using the default `PlayStorePaywallDelegate` for purchase handling. If you are using `RevenueCatPaywallDelegate`, configure RevenueCat to consume appropriate purchases. If you use a custom `HeliumPaywallDelegate`, you are responsible for consuming purchases.
</Note>

If your app sells consumable products (e.g., coins, credits, or tokens) and you want Helium to consume purchases for these products, relay these product IDs to Helium like so:

```kotlin theme={null}
Helium.config.consumableIds = setOf("coins_100", "gems_50")
```

## Checking Subscription Status & Entitlements

<Note>
  If you use an external payment processor like Stripe, Helium's entitlement helpers may not be reliable. We recommend implementing your own entitlement checking in that case. If you use Stripe with RevenueCat, we recommend using [RevenueCat's entitlement APIs](https://www.revenuecat.com/docs/getting-started/entitlements) instead.
</Note>

The Helium SDK provides multiple ways to check user entitlements and subscription status.

<Accordion title="Entitlement Helper Methods">
  `hasAnyEntitlement()` Checks if the user has purchased any subscription or non-consumable product.

  `hasAnyActiveSubscription()` Checks if the user has any active subscription.

  `hasEntitlementForPaywall(trigger: String)` Checks if the user has entitlements for any product in a specific paywall. Returns `null` if paywall configuration hasn't been downloaded yet.
</Accordion>

#### Example Usage

<Tip>
  Check entitlements before showing paywalls to avoid showing a paywall to a user who should not see it.
</Tip>

<CodeGroup>
  ```kotlin Use dontShowIfAlreadyEntitled with presentPaywall theme={null}
  Helium.presentPaywall(
      trigger = "my_paywall_trigger",
      config = PaywallPresentationConfig(
          dontShowIfAlreadyEntitled = true,
      ),
      onPaywallNotShown = { paywallNotShownReason ->
          // handle paywall not shown
      }
  )
  ```

  ```kotlin Check before showing paywall theme={null}
  val hasActiveSubscription = Helium.entitlements.hasAnyActiveSubscription()
  if (hasActiveSubscription) {
      // access premium content
  } else {
      // show paywall
  }
  ```
</CodeGroup>

## Fallbacks

It is highly recommended that you set up [fallbacks](/guides/fallback-bundle) in the uncommon case where a paywall fails to display. Please follow the linked guide to do so.

Note that if you attempt to display a paywall while it is still being downloaded, a loading state will show.

By default, Helium will show this loading state as needed (a shimmer view for up to 7 seconds). You can configure this behavior:

```kotlin theme={null}
Helium.config.defaultLoadingBudgetInMs = 5000  // 5 seconds
Helium.config.defaultLoadingView = myCustomLoadingView  // optional
```

If the budget expires before the paywall is ready, a fallback paywall will show if available. Otherwise, the loading state will hide and a `PaywallOpenFailed` event will be dispatched.

<Info>
  See the [Fallbacks Guide](/guides/fallback-bundle) for more details on downloading and configuring fallbacks.
</Info>

## Advanced

<Accordion title="Checking Download Status">
  <Note>
    In most cases there is no need to check download status. Helium will display a loading indication if a paywall is presented before download has completed.
  </Note>

  The `downloadStatus` is a Kotlin `Flow` that emits `HeliumConfigStatus` states. The possible states are:

  * `HeliumConfigStatus.NotYetDownloaded`: The initial state before the download has started.
  * `HeliumConfigStatus.Downloading`: Indicates that the paywall configuration is currently being downloaded.
  * `HeliumConfigStatus.DownloadFailure`: Indicates that the paywall configuration download has failed.
  * `HeliumConfigStatus.DownloadSuccess`: Indicates that the paywall configuration has been successfully downloaded.

  Here's how you can observe the `downloadStatus` flow in your Activity or Fragment:

  ```kotlin theme={null}
  // In your Activity or Fragment
  lifecycleScope.launch {
      Helium.shared.downloadStatus.collect { status ->
          when (status) {
              is HeliumConfigStatus.NotYetDownloaded -> {
                  // Handle not yet downloaded state
              }
              is HeliumConfigStatus.Downloading -> {
                  // Handle downloading state
              }
              is HeliumConfigStatus.DownloadFailure -> {
                  // Handle download failure
              }
              is HeliumConfigStatus.DownloadSuccess -> {
                  // Handle download success
              }
          }
      }
  }
  ```
</Accordion>

<Accordion title="Hiding Paywalls Programmatically">
  You can programmatically hide paywalls using:

  ```kotlin theme={null}
  // Hide the current paywall
  Helium.hidePaywall()

  // Hide all currently displayed paywalls
  Helium.hideAllPaywalls()
  ```
</Accordion>

<Accordion title="Reset Helium">
  Reset Helium entirely so you can call initialize again. Only for advanced use cases.

  ```kotlin theme={null}
  Helium.resetHelium()
  ```
</Accordion>
