This guide explains how to host Sportradar web widgets inside a native Android app using Jetpack Compose and WebView.
The integration pattern is reusable for all widgets. The only widget-specific parts are:
SIR("addWidget", ...)onTrack or onItemClickFollow the Sportradar web / JavaScript widget documentation for the loader bootstrap and SIR("addWidget", ...) call. On Android, host that page with a shared WebView, which provides layout helpers and JavaScript communication.
This tutorial is for:
WebView, Compose AndroidView, and JavaScript bridgesBy completing this tutorial, you will:
WebViewSIR(...) without reloadingWebView to the widget's content height inside native scroll containersBefore starting, ensure you have:
WebView availableaddWidget, updateWidget, setClientTheme, and related methods)[versions]
androidxWebkit = "1.16.0"
[libraries]
androidx-webkit-webkit = { module = "androidx.webkit:webkit", version.ref = "androidxWebkit" }implementation(local.androidx.webkit.webkit)The generic integration flow is:
Create a reusable WebView host with a message bridge, height observer, and optional JavaScript access.
Follow the Sportradar JS widget docs for the widgetloader bootstrap and SIR("addWidget", ...) call.
Load the HTML with loadDataWithBaseURL(...) and size the WebView from reported content height.
Receive widget callbacks through SportradarWidgetsSDK, and optionally call SIR(...) from Android without reloading.
The following pieces are enough for an Android integration:
WebViewonMessage for height and widget callbacksWebView reference for Android -> JSYou may choose to add code that builds widget HTML from parameters or automatically injects the Sportradar loader bootstrap. Those helpers are useful, but they are not required for a basic implementation.
WebViewSportradarWidgetsSDK message handlingHow you store or generate the HTML is an app concern, not part of the host.
The steps below walk through recreating the shared Android host in a reusable way. Capabilities are added progressively. After the host is in place, Using the Shared Android Host covers widget HTML, sizing, callbacks, and Android -> JS updates.
The shared Android host around WebView should:
loadDataWithBaseURL(...)SportradarWidgetsSDK message bridge with WebViewCompat.addWebMessageListener(...), scoped to trusted origins onlyWebView reference for evaluateJavascript(...), which enables Android -> JS communicationWebView hostBefore wiring the host, define the inputs it expects:
html: the widget page string you build from Sportradar's JS docsbaseUrl: a strict trusted HTTPS origin such as https://www.betradar.com#sr-widgetdata-sr-height-root on the element that should drive native auto-heightSportradarWidgetsSDK in these examples is just the JavaScript bridge name exposed by Android through . It is not a required Sportradar name. You can rename it to something else such as , as long as the Kotlin side and JavaScript side use the same name consistently.
Start with a Compose host that renders a WebView, sizes from reported height, and keeps a reference for later evaluateJavascript(...) calls:
var widgetHeightDp by remember { mutableStateOf(1) }
var webViewRef by remember { mutableStateOf<WebView?>(null) }
AndroidView(
modifier = Modifier.height(widgetHeightDp.dp),
Use the reported height to size the WebView when the widget sits inside a native scroll container.
Use a strict HTTPS origin such as https://www.betradar.com for baseUrl. Avoid * and avoid HTTP unless you fully trust the network path.
With the host in place, build widget HTML and wire it into your Compose UI. The remaining steps keep the Android host generic while leaving widget-specific differences in the HTML / props layer.
Use Sportradar's web widget documentation for:
widgetloader bootstrap for your client IDSIR("addWidget", selector, widgetName, props)See Getting Started and the SIR API for the authoritative JavaScript patterns.
A minimal page shape:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html, body { margin: 0; padding: 0; }
SportradarWidgetsSDK posts height updates as a string message payload:
height
420Parse them from onMessage and apply Modifier.height(...):
WidgetWebMessageBridge { message ->
if (message.type == "height" && message.height != null) {
widgetHeightDp = message.height
} else {
// Handle non-height widget messages
}
}Keep the widget HTML minimal and let the shared Android host inject the height observer after the page loads.
Add the injection in your WebViewClient:
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view?.evaluateJavascript(installHeightObserverJs, null)
}Use a shared observer script like this:
(function() {
function heightRoot() {
return document.querySelector("[data-sr-height-root]") || document.body;
}
function postHeight() {
var el = heightRoot();
var height = Math.ceil(el
That script:
data-sr-height-rootdocument.body if the marker is missinggetBoundingClientRect().heightValidate that the message came from the main frame and the expected origin before mapping it into your structured event flow:
class WidgetWebMessageBridge(
private val onMessage: (WidgetBridgeEvent) -> Unit,
) : WebViewCompat.WebMessageListener {
override fun onPostMessage(
view: WebView,
message: WebMessageCompat,
sourceOrigin: Uri,
isMainFrame: Boolean
and handle it from the same shared callback:
WidgetWebMessageBridge { message ->
if (message.type == "height" && message.height != null) {
widgetHeightDp = message.height
} else {
// Handle regular widget callback messages here
}
}When the JS docs say to attach callbacks such as onTrack or onItemClick, forward them into native code through SportradarWidgetsSDK:
onTrack: function(eventName, eventData) {
if (window.SportradarWidgetsSDK && typeof window.SportradarWidgetsSDK.postMessage === "function") {
window.SportradarWidgetsSDK.postMessage(
["callback", "onTrack", JSON.stringify([eventName, eventData])].join("\n
Payloads should be JSON-serializable. For nested objects, stringify in JS if needed.
onItemClick: function(target, data) {
if (window.SportradarWidgetsSDK && typeof window.SportradarWidgetsSDK.postMessage === "function") {
window.SportradarWidgetsSDK.postMessage(
["callback", "onItemClick", JSON.stringify([String(target), JSON.stringify
Handle those messages in the same onMessage callback used for height.
This pattern stays the same for all widgets:
postMessage to SportradarWidgetsSDK using a simple string protocoltype (or equivalent) in KotlinOnly the callback name and payload shape change per widget.
Learn more about tracking in the Widget Tracking & Analytics Guide.
SIR(...) from Android (optional)To change match, theme, language, or other live props without rebuilding HTML, use the retained WebView reference:
webViewRef?.evaluateJavascript(
"""SIR("updateWidget", "#sr-widget", { matchId: $matchId });""",
null,
)
webViewRef?.evaluateJavascript("""SIR("setClientTheme", "sportradardark");""", null)
// SIR("changeLanguage", "de")Wait until the page is loaded before evaluating. Prefer these API calls over regenerating the HTML string, which reloads the loader and is slower.
Swapping widgets should not require changing the shared Android host. Change only the HTML you pass in, and how you interpret the bridge messages.
The Android host, height observer, and bridge remain the same.
For a minimal integration, these two pieces are enough:
WebView host with height handling and JS bridge registrationThe shared Android WebView, SportradarWidgetsSDK, height reporting, and base URL handling stay the same across every widget.
Widget name, props, loader options, and callbacks live in the HTML / props layer only.
Use the bridge height messages to drive Modifier.height(...) instead of letting the WebView stretch arbitrarily inside native layouts.
Call SIR(...) through the retained WebView instead of rebuilding the HTML string for every live update.
addWidgetupdateWidget, themes, language, and other methodsonTrack and analytics patternsFor the message bridge shown below, add the Jetpack WebKit dependency:
WebViewCompat.addWebMessageListener(...)srBridgeNotes for Android hosting:
data-sr-height-root on the element that should drive native auto-height, usually the mount div. The shared Android host can observe that element and report its height back through the JS bridge. If omitted, height can fall back to document.body.sportradar) with your assigned client ID in production.If you want a fallback until the widget reports its real size, start from a small default or a minimum native height and replace it once the first height message arrives.
SIR("addWidget", "#sr-widget", "match.lmtPlus", {
matchId: 61591316,
layout: "single",
onTrack: function(eventName, eventData) { /* post to SportradarWidgetsSDK */ }
});