Playbooks
Moments Prefetch Integration O...
Android β Moments Prefetch Integration Guide
23 min
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 this mode, the sdk handles offer fetching internally a hidden 0Γ0 webview is initialized in advance, allowing offers to be cached before checkout begins this results in an instant display of offers during checkout, without additional network calls 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, offers 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 check out the momentscience android demo on github 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 valid momentscience sdk id , which you can obtain by following these steps environment kotlin version 1 6 0 or higher minimum android sdk version 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 add dependencies open your app/build gradle kts and add the following build gradle kts 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 \<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 uses 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 see offersview\ kt in the demo app for working implementation add javascript interface for sdk events on a screen that runs before the offer display screen, insert a 0Γ0 webview to silently prefetch offers make sure to enable javascript enable dom storage enable js native communication to receive callbacks like ads found from the sdk, implement a javascriptinterface and attach it to the webview adpxjavascriptinterface // 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 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 } load prefetch template with user data load adpx template html from your assets and inject runtime values like sdkid , adpxuser , and sdk cdn url 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 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" ) } see adpxhtmltemplate kt , offersviewmodel kt and adpx template html in the demo app for more details option 2 api prefetch mode 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 for complete details on moments api, refer to the moments api documentation https //docs adspostx com/moments api#rqin send a post request to the moments api moments api endpoint post https //api adspostx com/native/v4/offers json build the request parameters include the following api key as a query parameter a user payload in the request body custom user agent in the request headers validate optional parameters (if used) loyaltyboost must be " 0 ", " 1 ", or " 2 " creative must be " 0 " or " 1 " prepare and send the request use the following example code to construct and send the request fetchoffers 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 private val jsonresponse = mutablestateflow\<jsonelement?>(null) val jsonresponse stateflow\<jsonelement?> = jsonresponse asstateflow() val response = networkservice fetchoffers( sdkid = sdkid, payload = payload, isdevelopment = true ) createpayload 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" ) } see offersviewmodel kt and networkserviceimpl kt in the demo app for more details step 3 show offers in fullscreen webview once the ads found callback confirms that offers are available, you can render them using a full screen webview on your checkout screen use the checkoutview composable pass the necessary parameters to the checkoutview component this loads the offer experience using a dynamic html template ( checkout template html) checkoutview( sdkid = sdkid, jsonresponse = jsonresponse, isfromapiprefetch = isfromapiprefetch, offercount = offercount, // optional payload = payload, onbackpressed = { navcontroller popbackstack() } // handle back navigation ) html generation logic the offer experience is rendered by injecting data into checkout template html and loading it in the webview use the following utility method to generate the final html content 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 refer checkoutview\ kt , checkoutviewmodel kt , checkouthtmltemplate kt , 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 override shouldoverrideurlloading in the screen where offers are rendered (e g , checkoutview), override the webviewclientβs shouldoverrideurlloading() method 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 for a working example, see the implementation in checkoutview\ kt conclusion youβve now successfully integrated the momentscience sdk into 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\@m omentscience com