---
title: Android – MomentPerks Prefetch Integration Guide
slug: android-momentperks-prefetch-integration-guide
icon: {"faIcon":"fa-brands fa-android"}
docTags: 
createdAt: 2025-07-26T11:13:32.788Z
---

# Overview

This guide walks you through integrating the MomentScience Moments Solution into your Android app using Kotlin and Jetpack Compose. The SDK enables you to preload and display personalized offers during key checkout moments to drive revenue and user engagement.

MomentScience offers two integration modes to suit your technical preferences.

## Integration Modes

### SDK Prefetch Mode

In SDK Prefetch Mode, the SDK handles offer fetching internally by initializing a hidden 0×0 WebView in advance. This allows offers to be prefetched and cached before the user reaches the offer display screen (e.g. Checkout Screen).

As a result, offers appear instantly during checkout without requiring additional network requests.

- Simplest integration path, minimal setup required
- No need to manage HTTPS requests manually

### API Prefetch Mode

This mode gives your app full control over when and how offers are fetched. You initiate a native HTTPS request using your own logic and pass the response into the SDK at checkout time.

- Greater control over timing and payload
- Enables server-side optimizations and logging

**In both modes,&#x20;**&#x6F;ffers are rendered in a WebView at the checkout screen. Because offers are preloaded before checkout, the user experience remains fast and frictionless, even when no offers are found. If no offers are returned, you can choose to skip showing the offer display part altogether.

:::hint{type="success"}
Check out the [MomentScience Android Demo on GitHub](https://github.com/AdsPostX/examples/tree/main/android-native/MSSDKDemoApp-Android) to see the complete implementation, including offer fetching, WebView rendering, event handling, and external link support.
:::

## Requirements

To integrate the MomentScience SDK into your Android app, ensure the following prerequisites are met:

- A vali&#x64;**&#x20;MomentScience SDK ID**, which you can [obtain by following these steps.](https://docs.momentscience.com/getting-your-sdk-id)
- **Environment:**
  - **Kotlin version:** `1.6.0` or higher
  - **Minimum Android SDK version:&#x20;**`26`
  - **Target Android SDK version:** `35`

***

# Setup Instructions

## Step 1: Add Dependencies

To enable the MomentScience SDK in your Android app, you'll need to:

### Add HTML Assets

Copy the required HTML templates into your project. These files are used by the SDK to render the offers in a WebView\.You can place them in `assets/templates` folder.

**Required Files**

- [`adpx_template.html`](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/assets/templates/adpx_template.html)
- [`checkout_template.html`](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/assets/templates/checkout_template.html)

### Add Dependencies

Open your `app/build.gradle.kts` and add the following:

:::CodeblockTabs
build.gradle.kts

```javascript
dependencies {
    // Enables WebView support for rendering the SDK
    implementation("androidx.webkit:webkit:1.6.0")

    // Used for native API requests in API Prefetch Mode
    implementation("com.squareup.okhttp3:okhttp:4.10.0")

    // Used for parsing JSON offer payloads
    implementation("com.google.code.gson:gson:2.10")

    // Optional: Used for async image loading
    implementation("io.coil-kt:coil-compose:2.5.0")
}
```
:::

**What These Do:**

| **Library**               | **Purpose**                                                 |
| ------------------------- | ----------------------------------------------------------- |
| `androidx.webkit`         | Enables WebView rendering for the SDK                       |
| `okhttp3`                 | Required for making HTTP requests in API Prefetch Mode      |
| `gson`                    | Parses JSON responses into Kotlin data classes              |
| `coil-compose (optional)` | Loads creative assets like logos and banners asynchronously |

### Add Internet Permission

Include the following permission in your `AndroidManifest.xml`, outside the `<application>` tag:

```javascript
<uses-permission android:name="android.permission.INTERNET" />
```

This allows the SDK to fetch offers and tracking beacons over the network.

***

## Step 2: Prefetch Offers (SDK or API)

MomentScience offers two modes for prefetching offers before rendering them on your UI:

- **SDK Prefetch:&#x20;**&#x55;ses a hidden `0×0` WebView to silently fetch and cache offers before checkout.
- **API Prefetch:** Uses a native API call to fetch offers, which are then injected into the SDK display template.

In both modes, you can skip showing the checkout screen if no offers are found.

### Option 1: SDK Prefetch

In this approach, a hidden WebView preloads the SDK. When offers are available, the SDK triggers an `ads_found`callback event. It’s the lightest-touch integration, ideal when you want minimal native logic.

:::hint{type="info"}
See [OffersView.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/ui/OffersView.kt) in the demo app for working implementation.
:::

1. **Add JavaScript Interface for SDK Events:&#x20;**&#x4F;n a screen that runs before the offer display screen (e.g. Checkout Screen), insert a hidden 0×0 WebView to initiate SDK prefetching. Make sure to:
   1. Enable JavaScript
   2. Enable DOM Storage
2. **Enable JS-Native Communication:&#x20;**&#x54;o receive callbacks like `ads_found`from the SDK, implement a `JavaScriptInterface`and attach it to the WebView.

:::CodeblockTabs{indent="2"}
AdpxJavaScriptInterface

```swift
// Callback type
typealias AdpxCallbackHandler = (event: String, payload: String) -> Unit

// JavaScript interface to receive events from the SDK
class AdpxJavaScriptInterface(
    private val callbackHandler: AdpxCallbackHandler? = null
) {
    @JavascriptInterface
    fun adpxCallback(event: String, payload: String) {
        Log.d("AdpxJSInterface", "Event: $event, Payload: $payload")
        callbackHandler?.invoke(event, payload)
    }
}

// Attach to WebView
webView.addJavascriptInterface(
    AdpxJavaScriptInterface { event, payload ->
        viewModel.handleAdpxCallback(event, payload)
    },
    "Android"
)
```

handleAdpxCallback

```swift
private val _offerCount = MutableStateFlow(0)
val offerCount: StateFlow<Int> = _offerCount.asStateFlow()

private val _showCheckout = MutableStateFlow(false)
val showCheckout: StateFlow<Boolean> = _showCheckout.asStateFlow()

private val _jsonResponse = MutableStateFlow<JsonElement?>(null)
val jsonResponse: StateFlow<JsonElement?> = _jsonResponse.asStateFlow()

fun handleAdpxCallback(event: String, payload: String) {
    when (event) {
        "ads_found" -> handleAdsFound(payload)
    }
}

private fun handleAdsFound(payload: String) {
    try {
        val jsonObject = JSONObject(payload)
        if (jsonObject.has("response")) {
            val responseString = jsonObject.get("response").toString()
            val jsonElement = JsonParser.parseString(responseString)

            // Only set JSON response if this is from WEB prefetch
            if (_prefetchMode.value == PrefetchMode.WEB) {
                setJsonResponse(jsonElement)
            }

            try {
                val dataObject = jsonElement.asJsonObject.get("data")?.asJsonObject
                if (dataObject != null && !dataObject.isJsonNull) {
                    val offersArray = dataObject.get("offers")?.asJsonArray
                    if (offersArray != null && !offersArray.isJsonNull) {
                        val count = offersArray.size()
                        updateOffersCount(count)
                        setShowCheckout(true)
                        Log.d("AdpxCallback", "Found $count offers")
                    }
                }
            } catch (e: Exception) {
                Log.e("AdpxCallback", "Error parsing offers from response", e)
            }

            Log.d("AdpxCallback", "Successfully updated ViewModel with response")
        } else {
            Log.e("AdpxCallback", "Payload does not contain 'response' key")
        }
    } catch (e: Exception) {
        Log.e("AdpxCallback", "Error parsing ads_found payload", e)
    }
}

fun updateOffersCount(count: Int) {
    _offerCount.value = count
}
```
:::

3. **Load Prefetch Template with User Data:&#x20;**&#x4C;oad`adpx_template.html` from your assets and inject runtime values like `sdkId`, `AdpxUser`, and `SDK_CDN_URL`

:::CodeblockTabs{indent="2"}
```kotlin
fun generate(
    sdkId: String,
    context: Context,
    payload: Map<String, String>? = null
): String {
    requireNotNull(context) { "Context must not be null" }

    // Load HTML template from assets
    val templateContent = readAssetFile(context, "templates/adpx_template.html")

    // Create payload as JSON
    val gson = Gson()
    val userPayloadJson = payload?.let { gson.toJson(it) } ?: "{}"

    // Replace placeholders in HTML
    return templateContent
        .replace("%%SDK_ID%%", sdkId)
        .replace("%%SDK_CDN_URL%%", AppConfig.SDK_CDN_URL)
        .replace("window.AdpxUser = {};", "window.AdpxUser = $userPayloadJson;")
}

```

createPayload

```swift
private fun createPayload(): Map<String, String> {
    return mapOf(
        "adpx_fp" to "<unique_value>",
        "pub_user_id" to "<unique_value>",
        "ua" to "<user_agent_value>",
        "themeId" to "demo", // pass valid themeId here
        "placement" to "checkout"
    )
}

```
:::

:::hint{type="info"}
See [AdpxHtmlTemplate.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/templates/AdpxHtmlTemplate.kt), [OffersViewModel.kt ](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/viewmodels/OffersViewModel.kt)and [adpx\_template.html](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/assets/templates/adpx_template.html) in the demo app for more details.
:::

### Option 2: API Prefetch Mode&#x20;

In this mode, your app fetches offers **directly from the Moments API** using a native HTTP POST request. The fetched response is then passed into the MomentScience SDK for rendering at checkout.

:::hint{type="success"}
For complete details on MomentPerks API, refer to the [MomentPerks API](docId\:zpOL4DX0Bohl2AOoyZrLQ).
:::

1. **Send a&#x20;**`POST`**request to the Moments API:**

:::CodeblockTabs{indent="2"}
Moments API Endpoint

```javascript
POST https://api.adspostx.com/native/v4/offers.json
```
:::

2. **Build the Request Parameters:&#x20;**&#x49;nclude the following:
   1. `api_key` as a query parameter
   2. A user payload in the request body
   3. Custom `User-Agent` in the request headers
3. **Validate Optional Parameters (if used):&#x20;**
   1. `loyaltyboost `must be "`0`", "`1`", or "`2`"
   2. `creative `must be "`0`" or "`1`"
4. **Prepare and Send the Request:&#x20;**&#x55;se the following example code to construct and send the request:

:::CodeblockTabs{indent="2"}
fetchOffers

```kotlin
override suspend fun fetchOffers(
    sdkId: String,
    payload: Map<String, String>? = null,
    loyaltyboost: String?,
    creative: String?,
    campaignId: String? = null,
    isDevelopment: Boolean = false
): JsonElement = withContext(Dispatchers.IO) {
    try {
        validateParameters(loyaltyboost, creative)

        val urlBuilder = StringBuilder(baseUrl).apply {
            append("?api_key=$sdkId")
            loyaltyboost?.let { append("&loyaltyboost=$it") }
            creative?.let { append("&creative=$it") }
            campaignId?.let { append("&campaignId=$it") }
        }

        val requestJson = JsonObject().apply {
            payload?.forEach { (key, value) -> addProperty(key, value) }
            if (isDevelopment) addProperty("dev", "1")
        }

        val jsonBody = gson.toJson(requestJson)
        val requestBody = jsonBody.toRequestBody("application/json".toMediaType())

        val userAgent = payload?.get("ua")?.takeIf { it.isNotBlank() }
            ?: UserAgentUtil.getUserAgent()

        val request = Request.Builder()
            .url(urlBuilder.toString())
            .post(requestBody)
            .header("User-Agent", userAgent)
            .build()

        val response = client.newCall(request).execute()
        if (!response.isSuccessful) {
            val errorBody = response.body?.string()
            Log.e(TAG, "Error response: $errorBody")
            throw NetworkError.ServerError("Server returned ${response.code}: $errorBody")
        }

        val responseBody = response.body?.string()
            ?: throw NetworkError.ServerError("Empty response body")

        JsonParser.parseString(responseBody)
    } catch (e: Exception) {
        Log.e(TAG, "Network error", e)
        throw when (e) {
            is NetworkError -> e
            is IOException -> NetworkError.ServerError(e.message ?: "Unknown server error")
            else -> NetworkError.DecodingError(e.message ?: "Unknown error")
        }
    }
}
```

fetchOffers usage

```kotlin
private val _jsonResponse = MutableStateFlow<JsonElement?>(null)
val jsonResponse: StateFlow<JsonElement?> = _jsonResponse.asStateFlow()

val response = networkService.fetchOffers(
    sdkId = sdkId,
    payload = payload,
    isDevelopment = true
)

```

createPayload

```kotlin
private fun createPayload(): Map<String, String> {
    return mapOf(
        "adpx_fp" to "<unique_value>",
        "pub_user_id" to "<unique_value>",
        "ua" to "<user_agent_value>",
        "themeId" to "demo",       // Pass a valid themeId here
        "placement" to "checkout"
    )
}
```
:::

:::hint{type="info"}
See [OffersViewModel.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/viewmodels/OffersViewModel.kt) and [NetworkServiceImpl.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/services/NetworkServiceImpl.kt) in the demo app for more details.
:::

***

## Step 3: Show Offers in Fullscreen WebView&#x20;

Once the `ads_found` callback confirms that offers are available, you can render them using a full-screen WebView on your checkout screen.

1. **Use the&#x20;**[CheckoutView](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/ui/CheckoutView.kt)**&#x20;Composable:** Pass the necessary parameters to the CheckoutView component. This loads the offer experience using a dynamic HTML template (`checkout_template.html)`

:::CodeblockTabs{indent="2"}
```javascript
CheckoutView(
    sdkId = sdkId,
    jsonResponse = jsonResponse,
    isFromAPIPrefetch = isFromAPIPrefetch, // // you may not need this parameter as you are going to use only one of the approach.
    offerCount = offerCount, // Optional, require only if you want to show no of offers in UI.
    payload = payload,
    onBackPressed = { navController.popBackStack() } // Handle back navigation
)
```
:::

2. **HTML Generation Logic: &#x20;**&#x54;he offer experience is rendered by reading the `checkout_template.html` file, replacing predefined placeholders with actual offer data, and then loading the final HTML into the WebView.
   Use the provided utility method to perform placeholder replacement and generate the complete HTML content for display.

:::CodeblockTabs{indent="2"}
```javascript
fun generate(
    sdkId: String,
    offersCount: Int,
    jsonResponse: JsonElement?,
    isFromAPIPrefetch: Boolean = false,
    context: Context,
    payload: Map<String, String>? = null
): String {
    requireNotNull(context) { "Context must not be null to load HTML from assets" }

    val templateContent = readAssetFile(context, "templates/checkout_template.html")

    val autoLoadConfig = if (isFromAPIPrefetch) {
        "autoLoad: false"
    } else {
        "prefetch: true, autoLoad: true"
    }

    val userPayloadJson = payload?.let { Gson().toJson(it) } ?: "{}"

    val responseHandling = if (isFromAPIPrefetch) {
        """
        try {
            const responseData = ${jsonResponse?.toString() ?: "{}"};
            if (responseData && typeof responseData === 'object') {
                setTimeout(() => window.Adpx.setApiResponse(responseData), 100);
            }
        } catch (error) {
            console.error('Error parsing response: ' + error.message);
        }
        """.trimIndent()
    } else {
        "console.log('Adpx initialized successfully');"
    }

    return templateContent
        .replace("%%SDK_ID%%", sdkId)
        .replace("%%OFFERS_COUNT%%", offersCount.toString())
        .replace("%%AUTOLOAD_CONFIG%%", autoLoadConfig)
        .replace("%%RESPONSE_HANDLING%%", responseHandling)
        .replace("window.AdpxUser = {};", "window.AdpxUser = $userPayloadJson;")
        .replace("%%SDK_CDN_URL%%", AppConfig.SDK_CDN_URL)
}
```
:::

**Behavior Based on Prefetch Mode**

| **Mode**         | **Behavior**                                                            |
| ---------------- | ----------------------------------------------------------------------- |
| **SDK Prefetch** | SDK fetches offers automatically from the CDN. autoLoad: true           |
| **API Prefetch** | You inject the pre-fetched offer JSON via window\.Adpx.setApiResponse() |

Use the same `CheckoutView` regardless of the prefetch mode, only the generated HTML changes based on isFromAPIPrefetch.

:::hint{type="info"}
Refer [CheckoutView.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/ui/CheckoutView.kt), [CheckoutViewModel.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/viewmodels/CheckoutViewModel.kt), [CheckoutHtmlTemplate.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/templates/CheckoutHtmlTemplate.kt), [checkout\_template.html](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/assets/templates/checkout_template.html) in the demo app for more details.
:::

***

## Step 4: Open Clicks in External Browser

When a user taps on an offer, it’s recommended to open the offer URL in the device’s default browser, rather than opening it inside same WebView. This avoids trapping the user inside the checkout view and ensures a more familiar, secure experience.

1. Override `shouldOverrideUrlLoading`: In the screen where offers are rendered (e.g., CheckoutView), override the WebViewClient’s `shouldOverrideUrlLoading()` method:

:::CodeblockTabs{indent="2"}
```javascript
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    url?.let {
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(it))
        context.startActivity(intent)
        return true // URL was handled externally
    }
    return false // Let WebView handle it if URL is null
}
```
:::

**Best Practices**

- Use this override to handle external navigation for all outbound links in the SDK.
- Validate or sanitize URLs if you plan to intercept them for custom redirect logic.
- Avoid deep-linking back into your app unless you’re explicitly handling that behavior.

:::hint{type="info"}
For a working example, see the implementation in [CheckoutView.kt](https://github.com/AdsPostX/examples/blob/main/android-native/MSSDKDemoApp-Android/app/src/main/java/com/momentscience/apiwebdemoapp/ui/CheckoutView.kt).
:::

# Conclusion

You’ve now successfully integrated the Moments solutioninto your Android app using Kotlin and Jetpack Compose.

Whether you’re using Web SDK Prefetch Mode or API Prefetch Mode, your app is now equipped to deliver personalized offers that enhance the user experience and drive conversions.

***

📢 If you're running into any issues while going through the integration process, feel free to contact us at [help@momentscience.com](mailto\:help@Momentscience.com)
