Skip to main content

Kotlin (Android) Installation

Integrate the Asurion Widget into your native Android application using Android WebView. This approach loads the web widget inside a native WebView component, providing a seamless experience on Android devices.

Prerequisites

  • Android 5.0 (API level 21) or higher
  • Android Studio Arctic Fox or higher
  • Kotlin 1.5 or higher

Setup

Add internet permission to your AndroidManifest.xml:

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

Basic Usage

Create an Activity that renders the widget inside a WebView:

import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class AsurionWidgetActivity : AppCompatActivity() {

private lateinit var webView: WebView
private val widgetId = "<WIDGET_ID>"
private val context = mapOf("userId" to "user-123", "platform" to "android")
private val trackingParameters = mapOf("utm_source" to "mobile-app", "utm_medium" to "webview")

@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

webView = WebView(this).apply {
layoutParams = android.view.ViewGroup.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT
)
}
setContentView(webView)

setupWebView()
loadWidget()
}

@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mediaPlaybackRequiresUserGesture = false
loadWithOverviewMode = true
useWideViewPort = true
cacheMode = WebSettings.LOAD_DEFAULT
javaScriptCanOpenWindowsAutomatically = true
setSupportMultipleWindows(true)
}

webView.webViewClient = WebViewClient()
webView.setBackgroundColor(android.graphics.Color.TRANSPARENT)

// Handle window.open() calls from JavaScript
webView.webChromeClient = object : WebChromeClient() {
override fun onCreateWindow(
view: WebView?,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: android.os.Message?
): Boolean {
val result = view?.hitTestResult
val url = result?.extra
if (url != null) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
return false
}
}
}

private fun loadWidget() {
val html = generateWidgetHTML()
webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null)
}

private fun generateWidgetHTML(): String {
return """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
* { margin: 0; padding: 0; }
html, body { height: 100%; width: 100%; overflow: hidden; }
</style>
<script>
(function (w, d, s, o, f, a, js, fjs) {
w[o] = function () {
const [action, ...params] = arguments;
(w[o].q = w[o].q || []).push([action, ...params]);
};
(js = d.createElement(s)), (fjs = d.getElementsByTagName(s)[0]);
js.id = o;
js.src = f;
js.async = 1;
js.setAttribute('data-widget-id', a.id);
js.setAttribute('data-variant', a.variant);
w[o + '_context'] = a.context;
w[o + '_trackingParameters'] = a.trackingParameters;
fjs.parentNode.insertBefore(js, fjs);

// Forward widget events to native
w[o]('subscribe', 'state', function (state) {
AndroidBridge.onWidgetEvent(JSON.stringify({ type: 'state', payload: state }));
});
w[o]('subscribe', 'options', function (options) {
AndroidBridge.onWidgetEvent(JSON.stringify({ type: 'options', payload: options }));
});
})(
window,
document,
'script',
'_asurion_widget',
'https://app.widget-loader.service-initiation.asurion.com/widget-loader.js',
{
id: '$widgetId',
variant: 'embedded',
context: ${org.json.JSONObject(context)},
trackingParameters: ${org.json.JSONObject(trackingParameters)},
}
);
</script>
</head>
<body></body>
</html>
""".trimIndent()
}

override fun onBackPressed() {
if (webView.canGoBack()) {
webView.goBack()
} else {
super.onBackPressed()
}
}
}

Jetpack Compose Integration

For Jetpack Compose applications, create a composable wrapper:

import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import org.json.JSONObject

@SuppressLint("SetJavaScriptEnabled")
@Composable
fun AsurionWidget(
widgetId: String,
modifier: Modifier = Modifier,
context: Map<String, Any>? = null,
trackingParameters: Map<String, String>? = null
) {
val localContext = LocalContext.current

AndroidView(
modifier = modifier,
factory = { androidContext ->
WebView(androidContext).apply {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mediaPlaybackRequiresUserGesture = false
loadWithOverviewMode = true
useWideViewPort = true
cacheMode = WebSettings.LOAD_DEFAULT
javaScriptCanOpenWindowsAutomatically = true
setSupportMultipleWindows(true)
}
webViewClient = WebViewClient()
setBackgroundColor(Color.TRANSPARENT)

webChromeClient = object : WebChromeClient() {
override fun onCreateWindow(
view: WebView?,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: android.os.Message?
): Boolean {
val url = view?.hitTestResult?.extra
if (url != null) {
localContext.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
return false
}
}
}
},
update = { webView ->
val html = generateWidgetHTML(widgetId, context, trackingParameters)
webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null)
}
)
}

private fun generateWidgetHTML(
widgetId: String,
context: Map<String, Any>?,
trackingParameters: Map<String, String>?
): String {
val contextJson = context?.let { JSONObject(it).toString() } ?: "{}"
val trackingJson = trackingParameters?.let { JSONObject(it.toMap()).toString() } ?: "{}"

return """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
* { margin: 0; padding: 0; }
html, body { height: 100%; width: 100%; overflow: hidden; }
</style>
<script>
(function (w, d, s, o, f, a, js, fjs) {
w[o] = function () {
const [action, ...params] = arguments;
(w[o].q = w[o].q || []).push([action, ...params]);
};
(js = d.createElement(s)), (fjs = d.getElementsByTagName(s)[0]);
js.id = o;
js.src = f;
js.async = 1;
js.setAttribute('data-widget-id', a.id);
js.setAttribute('data-variant', a.variant);
w[o + '_context'] = a.context;
w[o + '_trackingParameters'] = a.trackingParameters;
fjs.parentNode.insertBefore(js, fjs);

// Forward widget events to native
w[o]('subscribe', 'state', function (state) {
AndroidBridge.onWidgetEvent(JSON.stringify({ type: 'state', payload: state }));
});
w[o]('subscribe', 'options', function (options) {
AndroidBridge.onWidgetEvent(JSON.stringify({ type: 'options', payload: options }));
});
})(
window,
document,
'script',
'_asurion_widget',
'https://app.widget-loader.service-initiation.asurion.com/widget-loader.js',
{
id: '$widgetId',
variant: 'embedded',
context: $contextJson,
trackingParameters: $trackingJson,
}
);
</script>
</head>
<body></body>
</html>
""".trimIndent()
}

// Usage in Compose
@Composable
fun WidgetScreen() {
AsurionWidget(
widgetId = "<WIDGET_ID>",
modifier = Modifier.fillMaxSize(),
context = mapOf("userId" to "user-123", "platform" to "android")
)
}

Configuration Options

OptionTypeRequiredDescription
idstringtrueYour unique widget identifier provided by Asurion
variantstringtrueWidget variant type. For Android WebView, only 'embedded' is supported
contextobjectfalseAdditional context data to pass to the widget. Can contain any key-value pairs
trackingParametersobjectfalseTracking parameters that will be appended to external links within the widget. Must be string key-value pairs

Communicating with the Widget

You can send commands to the widget using the evaluateJavascript method:

class AsurionWidgetActivity : AppCompatActivity() {

private lateinit var webView: WebView

// ... onCreate, setupWebView, loadWidget, generateWidgetHTML ...

fun extendContext(data: Map<String, Any>) {
val jsonString = org.json.JSONObject(data).toString()
webView.evaluateJavascript("window._asurion_widget('extendContext', $jsonString);", null)
}

fun extendTrackingParameters(params: Map<String, String>) {
val jsonString = org.json.JSONObject(params).toString()
webView.evaluateJavascript("window._asurion_widget('extendTrackingParameters', $jsonString);", null)
}
}

WebView Fragment

For more flexible usage, you can create a Fragment:

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.fragment.app.Fragment

class AsurionWidgetFragment : Fragment() {

private var webView: WebView? = null
private var widgetId: String = ""
private var context: Map<String, Any>? = null
private var trackingParameters: Map<String, String>? = null

companion object {
private const val ARG_WIDGET_ID = "widget_id"

fun newInstance(widgetId: String): AsurionWidgetFragment {
return AsurionWidgetFragment().apply {
arguments = Bundle().apply {
putString(ARG_WIDGET_ID, widgetId)
}
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
widgetId = arguments?.getString(ARG_WIDGET_ID) ?: ""
}

@SuppressLint("SetJavaScriptEnabled")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
webView = WebView(requireContext()).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)

settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mediaPlaybackRequiresUserGesture = false
loadWithOverviewMode = true
useWideViewPort = true
cacheMode = WebSettings.LOAD_DEFAULT
}

webViewClient = WebViewClient()
setBackgroundColor(android.graphics.Color.TRANSPARENT)
}

loadWidget()

return webView!!
}

private fun loadWidget() {
val html = generateWidgetHTML()
webView?.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null)
}

private fun generateWidgetHTML(): String {
// Same HTML generation as in Activity example
return """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
* { margin: 0; padding: 0; }
html, body { height: 100%; width: 100%; overflow: hidden; }
</style>
<script>
(function (w, d, s, o, f, a, js, fjs) {
w[o] = function () {
const [action, ...params] = arguments;
(w[o].q = w[o].q || []).push([action, ...params]);
};
(js = d.createElement(s)), (fjs = d.getElementsByTagName(s)[0]);
js.id = o;
js.src = f;
js.async = 1;
js.setAttribute('data-widget-id', a.id);
js.setAttribute('data-variant', a.variant);
w[o + '_context'] = a.context;
w[o + '_trackingParameters'] = a.trackingParameters;
fjs.parentNode.insertBefore(js, fjs);

// Forward widget events to native
w[o]('subscribe', 'state', function (state) {
AndroidBridge.onWidgetEvent(JSON.stringify({ type: 'state', payload: state }));
});
w[o]('subscribe', 'options', function (options) {
AndroidBridge.onWidgetEvent(JSON.stringify({ type: 'options', payload: options }));
});
})(
window,
document,
'script',
'_asurion_widget',
'https://app.widget-loader.service-initiation.asurion.com/widget-loader.js',
{
id: '$widgetId',
variant: 'embedded',
}
);
</script>
</head>
<body></body>
</html>
""".trimIndent()
}

fun extendContext(data: Map<String, Any>) {
val jsonString = org.json.JSONObject(data).toString()
webView?.evaluateJavascript(
"window._asurion_widget('extendContext', $jsonString);",
null
)
}

override fun onDestroyView() {
webView?.destroy()
webView = null
super.onDestroyView()
}
}

Subscribing to Widget Events

The widget forwards events to native code using a JavaScript interface (configured in Basic Usage above). Handle these events by adding a JavaScript interface to your WebView:

class AsurionWidgetActivity : AppCompatActivity() {

private lateinit var webView: WebView

// ...

@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
webView.settings.apply {
javaScriptEnabled = true
// ... other settings ...
}

webView.addJavascriptInterface(WidgetEventBridge(), "AndroidBridge")
// ... other setup ...
}

inner class WidgetEventBridge {
@android.webkit.JavascriptInterface
fun onWidgetEvent(eventJson: String) {
runOnUiThread {
val event = org.json.JSONObject(eventJson)
val type = event.getString("type")

when (type) {
"state" -> {
val payload = event.getString("payload")
println("Widget state: $payload")
}
"options" -> {
val payload = event.getJSONObject("payload")
println("Widget options: $payload")
}
}
}
}
}
}