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

# SDK Quickstart (Android)

> Integrate Helium into your Android app

<Tip>
  To get a better understanding of how paywalls are served, visit [here](/guides/how-does-helium-work).
</Tip>

<Note>
  Migrating from v0? View [the migration guide](/migrations/android-0-to-4).
</Note>

# Present a Paywall

### Install the SDK

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 dependency to your module-level `build.gradle.kts` file (e.g., `app/build.gradle.kts`):

```kotlin theme={null}
// app/build.gradle.kts

dependencies {
    implementation("com.tryhelium.paywall:core:4.0.0")
}
```

### Initialize Helium

<Tip>
  Find your API key [here](https://app.tryhelium.com/profile). If you do not have a Helium account set up yet, you can still integrate but will not be able to show a real paywall.
</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() {
            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() {
            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
```

### Show Your First Paywall 🎉

<Tip>
  Set up a trigger and workflow in the [dashboard](https://app.tryhelium.com/workflows) to show your desired paywall.
</Tip>

Call `presentPaywall` wherever you want to show a full-screen paywall:

```kotlin theme={null}
Helium.presentPaywall(
    trigger = "premium",
    onPaywallNotShown = { reason ->
        // Handle any scenario where the paywall does not show
    }
)
```

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

# Recommended Setup

Here are some common additional steps that you may want to consider.

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

### Helium Events

Helium dispatches various events during paywall presentation and purchase flow. You can optionally handle these events in your mobile app. You can also configure Helium to forward them to your [existing analytics provider](/guides/third-party-analytics).

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

#### Global Event Listener

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
        }
    }
}
```

### Fallback Paywalls

It is **highly recommended** that you set up "fallbacks" to handle the rare case when a paywall fails to display. Please follow [this guide](/guides/fallback-bundle) to do so.

<Tip>
  Do this *after* you have a paywall created that you want to use in production.
</Tip>

### Checking Subscription Status & Entitlements

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

<Accordion title="All 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>

# Advanced

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

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

### RevenueCat

<Note>
  By default, Helium will handle purchases for you! This section is typically for users who already use RevenueCat in their app.
</Note>

Helium integrates seamlessly with RevenueCat so you can continue to let RevenueCat handle your purchases and entitlements.

#### Install HeliumRevenueCat

Add the RevenueCat dependency to your module-level `build.gradle.kts`:

```kotlin theme={null}
// app/build.gradle.kts

dependencies {
    implementation("com.tryhelium.paywall:core:4.0.0")
    implementation("com.tryhelium.paywall:revenue-cat:4.0.0")
}
```

#### Configure Helium to use RevenueCat for Purchases

Simply use Helium's pre-built `RevenueCatPaywallDelegate`:

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

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

<Tip>
  It is best to do this configuration *before* you call `Helium.initialize`.
</Tip>

#### RevenueCat appUserID

If you ever [change the appUserID](https://www.revenuecat.com/docs/customers/identifying-customers#logging-in-after-configuration) of a user, keep Helium in sync with:

```kotlin theme={null}
Helium.identify.revenueCatAppUserId = Purchases.sharedInstance.appUserID
```

### Custom Purchase Handling

<Note>
  By default, Helium will handle purchases for you! This section is for those who want to implement custom purchase logic.
</Note>

<Tip>
  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.)
</Tip>

You can 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)
}
```

### Additional Features

<Note>
  For the full public API and detailed parameter documentation, see the inline docstrings in the SDK source. Import Helium in your project and use your IDE's autocomplete or jump-to-definition to explore all available methods and types.
</Note>

<Accordion title="Checking Download Status">
  <Tip>
    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.
  </Tip>

  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, for example after changing user traits that can affect the paywalls a user might see via targeting.

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