> ## Documentation Index
> Fetch the complete documentation index at: https://helium-promptless-document-light-dark-mode-override.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**

Thanks for getting started with Helium 🎈

Helium is an upsell experimentation and optimization platform for mobile apps of all sizes. We take your app’s existing paywalls and turn them into remotely editable templates that you can experiment with and optimize without waiting for new app releases.

Email [founders@tryhelium.com](mailto:founders@tryhelium.com) for help/questions.

## **Installation**

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

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

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

    dependencies {
        // Choose one of the UI modules:
        implementation("com.tryhelium.paywall:core:0.1.9")
        // Other app dependencies
    }
    ```
  </Tab>

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

    dependencies {
        // Choose one of the UI modules:
        implementation("com.tryhelium.paywall:compose-ui:0.1.9")
        // Other app dependencies
    }
    ```
  </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:0.1.9")
        // Other app dependencies
    }
    ```
  </Tab>
</Tabs>

## Initialize Helium

You need to initialize Helium before you can present a paywall. The best place to do this is in your `MainActivity`'s `onCreate()` method. When initializing, you must specify the environment, which can be `HeliumEnvironment.SANDBOX` for development and testing, or `HeliumEnvironment.PRODUCTION` for your live app. Note that for debug builds, the SDK will automatically force the sandbox environment.

```kotlin theme={null}
// In your MainActivity class's onCreate() method
import com.tryhelium.paywall.core.HeliumEnvironment

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val heliumPaywallDelegate = PlayStorePaywallDelegate(this) // Or your custom delegate

        Helium.initialize(
            context = this,
            apiKey = "YOUR_API_KEY",
            heliumPaywallDelegate = heliumPaywallDelegate,
            customUserId = "YOUR_CUSTOM_USER_ID", // Can be null if the user is anonymous
            customUserTraits = HeliumUserTraits(/* populate with relevant user properties */), // Optional
            environment = HeliumEnvironment.SANDBOX // Or HeliumEnvironment.PRODUCTION
        )
    }
}
```

## Observing Paywall Configuration Download Status

The Helium SDK provides a `downloadStatus` flow that you can observe to get updates on the status of the paywall configuration download. This is useful for showing loading indicators or handling download failures.

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

## HeliumPaywallDelegate

You can provide an implementation of the `HeliumPaywallDelegate` or use one of the default implementations that we have provided, such as `PlayStorePaywallDelegate` or `RevenueCatPaywallDelegate`.

<Tabs>
  <Tab title="PlayStorePaywallDelegate">
    Use the `PlayStorePaywallDelegate` to handle purchases using Google Play Billing.
  </Tab>

  <Tab title="RevenueCatPaywallDelegate">
    Use the `RevenueCatPaywallDelegate` to handle purchases if your app is using RevenueCat. Make sure you add the `revenue-cat` dependency as shown in the installation section.
  </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).
  </Tab>
</Tabs>

## Presenting Paywalls

To present a Paywall, ensure you have initialized the library. The SDK provides UI components that handle the underlying `PaywallWebViewManager`.

The first step to presenting a paywall is to create an `Intent` using the `createPaywallIntent` helper function. This function configures and returns an `Intent` that can be used to launch the `HeliumPaywallActivity`.

Here are the available options for `createPaywallIntent`:

* `context`: The Android `Context` required to create the intent. Use `this` in an Activity or `LocalContext.current` in a Composable.
* `trigger`: The specific paywall trigger you want to display. This is a required parameter.
* `fullscreen`: A `Boolean` to determine if the paywall should be displayed in fullscreen [immersive mode](https://developer.android.com/develop/ui/views/layout/immersive), where the system bars (status and navigation) are hidden. This is optional and defaults to `false`.
* `disableSystemBackNavigation`: A `Boolean` to control the system back button behavior. If `true`, the back button will not close the paywall. This is optional and defaults to `true`.

```kotlin theme={null}
// Create the intent to launch the paywall
val heliumIntent = createPaywallIntent(
    context = this, // or LocalContext.current in Compose
    trigger = "sdk_test"
)
```

Once you have your intent, you can launch it using either Jetpack Compose or the Android View System.

We provide two different ways of presenting the Paywall:

<Tabs>
  <Tab title="Jetpack Compose">
    The recommended way to present a paywall in Jetpack Compose is by launching the `HeliumPaywallActivity` using `rememberLauncherForActivityResult`. This approach allows you to receive a result back from the paywall, such as whether a purchase was successful.

    Here is an example of how to set it up in your Composable function:

    ```kotlin theme={null}
    val context = LocalContext.current
    val paywallLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // Handle successful purchase
        } else {
            // Handle cancellation
        }
    }

    Button(onClick = {
        paywallLauncher.launch(heliumIntent)
    }) {
        Text("Show Paywall")
    }
    ```

    Alternatively, if you are using `NavController`, you can add the paywall to your navigation graph using `buildHelium()`:

    ```kotlin theme={null}
    NavHost(
        navController = navController,
        startDestination = "landing",
        modifier = Modifier.fillMaxSize()
    ) {
        composable("landing") {
            LandingScreen(navController = navController)
        }
        buildHelium(
            navigationDispatcher = { command ->
                when (command) {
                    HeliumNavigationCommand.GoBack -> navController.popBackStack()
                    is HeliumNavigationCommand.Paywall -> navController.navigate("helium?trigger=${command.trigger}")
                }
            }
        )
    }
    ```

    To present a paywall, you can then navigate to the Helium route with a specific trigger:

    ```kotlin theme={null}
    // Navigate to the paywall
    navController.navigate("helium?trigger=sdk_test")
    ```
  </Tab>

  <Tab title="Android View System">
    You can present paywalls using either an `Activity` or a `Fragment`. Make sure you have initialized the Helium SDK as described in the "Initialize Helium" section before presenting the paywall.

    ##### Using HeliumPaywallActivity

    You can use `HeliumPaywallActivity` to display a paywall.

    To launch the paywall and handle the result, it is recommended to use `registerForActivityResult`.

    ```kotlin theme={null}
    // In your Activity or Fragment

    // Set up the ActivityResultLauncher
    val paywallLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // User has successfully interacted with the paywall.
            // For example, you might want to refresh the user's subscription status.
        }
    }

    // Launch your Paywall Activity
    paywallLauncher.launch(heliumIntent)
    ```

    ##### Using HeliumPaywallFragment

    Using the `HeliumPaywallFragment` requires you to create a subclass and implement the `HeliumNavigationDispatcher` interface.

    **1. Create your Paywall Fragment:**

    ```kotlin theme={null}
    // In your app, for example, MyPaywallFragment.kt
    class MyPaywallFragment : HeliumPaywallFragment() {
        override fun navigate(command: HeliumNavigationCommand) {
            when (command) {
                HeliumNavigationCommand.GoBack -> requireActivity().supportFragmentManager.popBackStack()
                is HeliumNavigationCommand.Paywall -> {
                    // Handle navigation to another paywall if needed
                }
            }
        }

        companion object {
            fun newInstance(trigger: String): MyPaywallFragment {
                return MyPaywallFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_TRIGGER, trigger)
                    }
                }
            }
        }
    }
    ```

    **2. Launch your Paywall Fragment:**

    ```kotlin theme={null}
    // To launch your paywall from an Activity or another Fragment
    val fragment = MyPaywallFragment.newInstance("sdk_test")
    supportFragmentManager.beginTransaction()
        .replace(R.id.fragment_container, fragment)
        .addToBackStack(null)
        .commit()
    ```
  </Tab>
</Tabs>

## PaywallEventHandlers

The Helium SDK allows you to listen for various paywall-related 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.
* `onOpenFailed`: Called when a paywall fails to open.
* `onCustomPaywallAction`: Called when a custom action is triggered from the paywall.

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

**Manual Management**
If you don't provide a `LifecycleOwner`, you are responsible for removing the listener to prevent memory leaks.

```kotlin theme={null}
// Add the listener
override fun onResume() {
    super.onResume()
    Helium.shared.addPaywallEventListener(paywallEventHandlers)
}

// Remove the listener to avoid leaks
override fun onPause() {
    super.onPause()
    Helium.shared.removeHeliumEventListener(paywallEventHandlers)
}
```

### 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}
// In your Activity or Fragment
import com.tryhelium.paywall.core.event.HeliumEventListener
import com.tryhelium.paywall.core.event.HeliumEvent
import com.tryhelium.paywall.core.event.PaywallOpen
import com.tryhelium.paywall.core.event.PaywallClose

class MyActivity : AppCompatActivity(), HeliumEventListener {

    override fun onResume() {
        super.onResume()
        // Register this class as the listener
        Helium.shared.addPaywallEventListener(this, this)
    }

    override fun onPause() {
        super.onPause()
        // Unregister to prevent memory leaks
        Helium.shared.removeHeliumEventListener(this)
    }

    override fun onHeliumEvent(event: HeliumEvent) {
        when (event) {
            is PaywallOpen -> {
                // Handle paywall open
            }
            is PaywallClose -> {
                // Handle paywall close
            }
            // ... handle other event types
        }
    }
}
```

### Event Timing and Guidelines

* **Event Timing**:
  * `onDismissed` is triggered only when a user manually dismisses a paywall (e.g., by tapping an 'X' button).
  * `onClose` is triggered *any time* the paywall is removed from the screen. This includes both when it's manually dismissed and when a successful purchase closes the paywall.
  * `onPurchaseSucceeded` is triggered as soon as the purchase is confirmed by the billing system.

* **Usage Guidelines**:
  * Use `onDismissed` for post-paywall navigation when the user's entitlement has not changed.
  * Use `onPurchaseSucceeded` to trigger your post-purchase flow, like showing a confirmation or premium onboarding.
  * Use `onClose` for general cleanup logic that should run regardless of how the paywall was closed.

## Fallbacks and Loading Budgets

If a paywall has not completed downloading when you attempt to present it, a loading state can be displayed. By default, Helium will show this loading state (a shimmer view) for up to 2 seconds (`2000ms`). You can configure this behavior, turn it off, or set trigger-specific loading budgets using the `HeliumFallbackConfig` object during initialization.

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

There are three options for fallbacks in the Android SDK:

* **Fallback bundles**: A pre-packaged paywall bundle stored in your app's `assets` directory.
* **Default fallback view**: A custom Android `View` to be used for all triggers.
* **Fallback view per trigger**: A map of trigger names to specific Android `View`s.

All of this is configured via the `HeliumFallbackConfig` object passed into `Helium.initialize()`.

Here are some examples:

**1. Providing a fallback bundle:**

Place your fallback JSON file in the `src/main/assets` directory of your module. Then, initialize Helium with the `fallbackBundleName`.

```kotlin theme={null}
// In your Activity's or Application's onCreate() method
Helium.initialize(
    // ... other parameters
    environment = HeliumEnvironment.SANDBOX, // Or HeliumEnvironment.PRODUCTION
    fallbackConfig = HeliumFallbackConfig.withFallbackBundle(
        fallbackBundleName = "fallback-bundle-name.json"
    )
)
```

**2. Providing a fallback view and configuring loading budgets:**

You can also provide a custom `View` and fine-tune the.

```kotlin theme={null}
// In your Activity's or Application's onCreate() method
val fallbackView = YourFallbackView(this) // Your custom fallback view

Helium.initialize(
    // ... other parameters
    environment = HeliumEnvironment.SANDBOX, // Or HeliumEnvironment.PRODUCTION
    fallbackConfig = HeliumFallbackConfig(
        fallbackView = fallbackView,
        useLoadingState = true, // Show a loading state before fallback
        loadingBudgetInMs = 3000, // Global loading budget (in milliseconds)
        perTriggerLoadingConfig = mapOf(
            "onboarding" to HeliumFallbackConfig(loadingBudgetInMs = 4000),
            "quick_upgrade" to HeliumFallbackConfig(useLoadingState = false)
        )
    )
)
```

## Testing

Docs here coming soon!
