Get started quickly with the Virtual Stadium Chat component by following this comprehensive integration guide. This guide covers the essential steps to integrate real-time messaging, bet sharing, flash bets, and AI-powered insights into your Android application.
To integrate the Chat component:
This guide will walk you through each step with code examples and best practices.
Add the Chat component to your Compose activity with minimal required configuration. This basic setup provides full chat functionality with real-time messaging.
Required Parameters:
jwtToken String required
JWT authentication token required to initialize the UI SDK and authenticate the user.
channelId String required
The unique identifier of the chat channel to display.
languageCode LanguageCode required
Language code for localizing SDK content and fetching localized feeds (e.g., LanguageCode.EN, LanguageCode.ES).
The jwtToken, channelId, and languageCode are minimum required parameters. See Configuration Parameters for additional options.
MainActivity.kt:
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import ag.sportradar.virtualstadium.uisdk.compose.Chat
import ag.sportradar.virtualstadium.uisdk.LanguageCode
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
/**
* TODO(developer): Replace <your-jwt-token> with your actual JWT token
* and <your-channel-id> with your actual channel ID before running this code.
*/
setContent {
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
modifier = Modifier.fillMaxSize()
)
}
}
}Add the Chat component using traditional Android Views with XML layout. The component is initialized programmatically with the same required parameters.
Required Parameters:
jwtToken String required
JWT authentication token required to initialize the UI SDK and authenticate the user.
channelId String required
The unique identifier of the chat channel to display.
languageCode LanguageCode required
Language code for localizing SDK content and fetching localized feeds.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ag.sportradar.virtualstadium.uisdk.views.VirtualStadiumChatView
android:id="@+id/chat_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>The Chat component supports various configuration options to customize appearance, behavior, and functionality.
jwtToken String required
JWT authentication token required to initialize the UI SDK and authenticate the user.
channelId String required
The unique identifier of the chat channel to display.
languageCode LanguageCode required
Language code for localizing SDK content and fetching localized feeds (e.g., LanguageCode.EN, LanguageCode.ES).
modifier Modifier optional
Compose modifier for layout customization (Compose only).
uiSettings UISettings optional
Customization settings for theming, fonts, icons, and UI behavior. Default is defaultUISettings(). See UI Settings.
betSlipListProvider (suspend () -> List<BetPayload>)? optional
Lambda function that returns a list of user's bet slips for sharing. If null, the "Share Bet" button is hidden.
onSharedBetsClicked (() -> Unit)? optional
Callback invoked when the user clicks the "Shared Bets" button. Use this to perform actions before showing the shared bets screen.
onCopyToBetSlipClicked ((BetPayload) -> Unit)? optional
Callback invoked when a user copies a bet from the chat. Returns the BetPayload to add to your bet slip.
analyticsProvider AnalyticsProvider optional
Provider for logging analytics events throughout the SDK. Default is AnalyticsProvider().
onContactSupportClicked ((InfractionReason?) -> Unit)? optional
Callback for the "Contact Support" button shown in error/infraction screens. If null, the button is hidden.
onTagClicked (Tag) -> Unit optional
Callback invoked when a user clicks a tag in a message. Default is empty function {}.
onProfileClicked ((String) -> Unit)? optional
Callback invoked when a user clicks a profile. Receives the user ID. If null, profile icons are hidden.
onLogout ((() -> Unit) -> Unit)? optional
Callback to handle logout initiated from within the SDK.
flashBetState FlashBetState optional
State object for managing Flash Bet functionality. Default is rememberFlashBetState(). See Flash Bet.
betInsightsState BetInsightsState optional
State object for managing Bet Insights functionality. Default is rememberBetInsightsState(). See Bet Insights.
matchStatusState MatchStatusState optional
State object for controlling match status behavior. Default is rememberMatchStatusState(). See Match Status.
Flash Bet enables time-sensitive quick betting triggered by live match events. Users receive real-time betting opportunities with a countdown timer.
class FlashBetState {
// Callback triggered when a flash bet event arrives
var onFlashBetCallback: (suspend () -> Market?)?
// Update the currently displayed flash bet market
suspend fun updateMarket(market: Market)
// Reset the flash bet state
fun reset()
}Timeout Behavior If you don't provide a market within 60 seconds of the flash bet trigger, the flash bet will automatically be canceled and move to the next event in the queue (if any).
Market suspension timeout can be configured in the moderation panel or via the moderation API.
data class Market(
val id: String,
val name: String,
val status: MarketStatus,
val outcomes: List<Outcome>,
val onOutcomeClicked: (Outcome) -> Unit,
)
enum class MarketStatus {
ACTIVE, // Market is active and accepting bets
SUSPENDED, // Market is suspended, timer pauses
DEACTIVATED // Market is deactivated, outcomes disabled
}
data class Outcome(
val id: String,
val name: String,
val odds: Double,
val status: OutcomeStatus
)Flash Bet Implementation - Compose:
val flashBetState = rememberFlashBetState()
// Set up callback for new flash bet events
flashBetState.onFlashBetCallback = {
/**
* TODO(developer): Replace with your actual market fetching logic
*/
// Fetch market data from your backend
val market = Market(
id = "market_123",
name = "Next Goal Scorer",
status = MarketStatus.ACTIVE,
outcomes = listOf(
Outcome(
id = "outcome_1",
name = "Player A",
odds = 2.5,
status = OutcomeStatus.ACTIVE
),
Outcome(
id = "outcome_2",
name = "Player B",
odds = 3.2,
status = OutcomeStatus.ACTIVE
)
),
onOutcomeClicked = { outcome ->
// Handle outcome selection
placeBet(outcome)
Toast.makeText(
context,
"Bet placed: ${outcome.name}",
Toast.LENGTH_SHORT
).show()
}
)
market
}
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
flashBetState = flashBetState
)Use market status to control flash bet behavior during different match scenarios. This allows you to pause, resume, or deactivate markets based on live events.
Market Status Control:
// Suspend the market (e.g., due to a goal)
flashBetState.updateMarket(
currentMarket.copy(status = MarketStatus.SUSPENDED)
)
// Resume the market
flashBetState.updateMarket(
currentMarket.copy(status = MarketStatus.ACTIVE)
)
// Deactivate the market
flashBetState.updateMarket(
currentMarket.copy(status = MarketStatus.DEACTIVATED)
)Bet Insights provides AI-powered betting suggestions based on Sportradar's real-time data feeds. The feature displays the most relevant markets and outcomes for the current match.
matchId via the moderation panel or APIImportant Notes
onNewInsights callback returns all available markets and outcomes from the feedclass BetInsightsState {
// Callback triggered when new insights are fetched
var onNewInsights: ((List<OutcomePayloadRequest>) -> Unit)?
// Callback triggered when user clicks an insight outcome
var onInsightOutcomeClicked: ((eventId: String, outcomeId: String) -> Unit)?
// Update insights to display
suspend fun updateInsights(payload: OutcomesPayload)
}The useClientData flag determines naming sources:
useClientData = true - Use your custom market and outcome namesuseClientData = false - Use names from Sportradar's feedExample:
The SDK provides this data for you to map to your betting system:
/**
* Request structure from the SDK insights feed
*/
data class OutcomePayloadRequest(
val eventId: String,
val marketId: String,
val outcomeId: String,
val specifier: SpecifierRequest?,
)
data class SpecifierRequest(
val name: String, // e.g., "Total Goals"
val value: String, // e.g., "Over 2.5"
)Your app builds this structure to provide insights data back to the SDK:
/**
* Payload you provide to the SDK
*/
data class OutcomesPayload(
val outcomes: List<ClientInsightData> = emptyList(),
val useClientData: Boolean = false,
)
data class ClientInsightData(
val event: InsightEvent,
val market: InsightMarket,
val outcome: InsightOutcome? = null,
)data class InsightEvent(
val id: String,
val teams: List<InsightTeam>,
val isLive: Boolean,
val sport: InsightSport,
)
data class InsightMarket(
val id: String,
val name: String,
val status: Status,
val specifier: Specifier? = null,
)data class InsightOutcome(
val id: String,
val name: String,
val status: Status,
val competitor: String? = null,
val oddsDecimal: Double,
val odds: String,
val isSelected: Boolean = false,
)
enum class Status {
ACTIVE, // Available for betting
DEACTIVATED, // Temporarily deactivated
SUSPENDED, // Temporarily suspended
}Bet Insights Implementation - Compose:
val betInsightsState = rememberBetInsightsState()
// Handle new insights data
betInsightsState.onNewInsights = { outcomePayloadRequests ->
/**
* TODO(developer): Replace with your actual market data fetching logic
*/
lifecycleScope.launch {
// Map SDK requests to your betting data
val outcomes = outcomePayloadRequests.map { request ->
val myMarket = fetchMarketData(
eventId = request.eventId,
marketId = request.marketId,
outcomeId = request.outcomeId
)
ClientInsightData(
event = InsightEvent(
id = request.eventId,
teams = listOf(
InsightTeam(name = "Team A"),
InsightTeam(name = "Team B")
),
isLive = true,
sport = InsightSport(id = "1", name = "Soccer")
),
market = InsightMarket(
id = request.marketId,
name = myMarket.name,
status = Status.ACTIVE,
specifier = request.specifier?.let {
Specifier(it.value)
}
),
outcome = InsightOutcome(
id = request.outcomeId,
name = myMarket.outcomeName,
status = Status.ACTIVE,
oddsDecimal = myMarket.odds,
odds = myMarket.odds.toString()
)
)
}
// Update insights display
betInsightsState.updateInsights(
OutcomesPayload(
outcomes = outcomes,
useClientData = true
)
)
}
}
// Handle outcome clicks
betInsightsState.onInsightOutcomeClicked = { eventId, outcomeId ->
// Add to bet slip or navigate to betting screen
addToBetSlip(eventId, outcomeId)
}
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
betInsightsState = betInsightsState
)Enable mock insights for testing without a real match. This is useful during development to test the UI and interaction flows.
val uiSettings = defaultUISettings(
otherSettings = OtherSettings(
useMockedInsights = true
)
)
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
uiSettings = uiSettings,
betInsightsState = betInsightsState
)Use a mock data provider to generate test insights:
MockedInsightsProvider.kt:
object MockedInsightsProvider {
fun getMockedInsights(
outcomes: List<OutcomePayloadRequest>,
useRandomOdds: Boolean = false
): OutcomesPayload {
val clientData = outcomes.mapIndexed { idx, request ->
val randomOdds = if (useRandomOdds) {
Random.nextDouble(1.4, 2.0)
.toBigDecimal()
.setScale(1, RoundingMode.HALF_UP)
.toDouble()
} else 2.5
ClientInsightData(
event = InsightEvent(
id = request.eventId,
teams = listOf(
InsightTeam(name = "Team A"),
InsightTeam(name = "Team B")
),
isLive = true,
sport = InsightSport(id = "1", name = "Soccer")
),
market = InsightMarket(
id = request.marketId,
name = "Market ${idx + 1}",
status = Status.ACTIVE,
specifier = request.specifier?.let {
Specifier(it.value)
}
),
outcome = InsightOutcome(
id = request.outcomeId,
name = "Outcome ${idx + 1}",
status = Status.ACTIVE,
oddsDecimal = randomOdds + idx,
odds = (randomOdds + idx).toString()
)
)
}
return OutcomesPayload(
outcomes = clientData,
useClientData = true
)
}
}Enable users to share their bet slips and copy bets from other community members. This creates a social betting experience where users can learn from and engage with each other's betting strategies.
If betSlipListProvider is null, the "Share Bet" button will be automatically hidden from the UI.
Bet Sharing Example:
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
// Provide user's bet slips for sharing
betSlipListProvider = {
/**
* TODO(developer): Replace with your bet slip fetching logic
*/
// Fetch from your bet slip system
getBetSlipsFromDatabase()
},
// Handle shared bets button click
onSharedBetsClicked = {
// Optional: perform action before showing shared bets
logAnalyticsEvent("shared_bets_opened")
},
// Handle copying a bet
onCopyToBetSlipClicked = { betPayload ->
// Add to user's bet slip
addToBetSlip(betPayload)
Toast.makeText(
context,
"Bet copied to your slip!",
Toast.LENGTH_SHORT
).show()
}
)data class BetPayload(
val id: String,
val betSlipId: String = "",
val betType: BetType,
val currency: String,
val combinedOdds: Odds,
val stake: Stake? = null,
val payout: Payout? = null,
val cashOut: CashOut? = null,
val bets: List<Bet>,
)
enum class BetType {
BET_BUILDER,
MULTI_BET,
SAME_GAME_MULTI,
SINGLE,
NONE,
}
data class Odds(
val decimalValue: Float,
val displayValue: String? = null,
)data class Bet(
val id: String,
val betType: BetType,
val event: BetEvent,
val markets: List<BetMarket>,
val odds: Odds? = null,
)
data class BetEvent(
val id: String,
val name: String? = null,
val teams: List<Team> = emptyList(),
)
data class BetMarket(
val id: String,
val name: String,
val outcomes: List<Outcome>,
)
data class Outcome(
val id: String,
val name: String,
val odds: Odds? = null,
)Control how match status affects theming for Flash Bets and Insights.
enum class MatchStatusBehavior {
LIVE, // Force live theming
AUTOMATIC, // Automatic based on feed (default)
DISABLED, // Disable automatic status changes
}Match Status - Compose:
val matchStatusState = rememberMatchStatusState()
// Update match status behavior
LaunchedEffect(Unit) {
matchStatusState.updateMatchStatus(
MatchStatusBehavior.LIVE
)
}
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
matchStatusState = matchStatusState
)Customize the appearance and behavior of the Chat component through comprehensive theming options.
Control all color aspects of the UI including general colors, Flash Bet theming, and Insights theming. You can customize colors for both pre-match and live states.
Custom Theme Example:
val customColorScheme = lightColorScheme(
primary = Color(0xFF1976D2),
secondary = Color(0xFFFF6F00),
background = Color(0xFFFAFAFA),
// ... other colors
)
val themeSettings = defaultThemeSettings(
colorScheme = customColorScheme,
// Customize specific colors
loadingIndicatorColor = Color.Red,
inputFieldBackgroundColor = Color.White,
// Flash Bet colors
flashBetPrematchColorPalette = FlashBetColorPalette.FlashBetPrematchColors(
colorScheme = customColorScheme,
flashBetBackgroundColor = Color(0xFF1E88E5),
flashBetOutcomeBackground = Color.White
),
flashBetLiveColorPalette = FlashBetColorPalette.FlashBetLiveColors(
colorScheme = customColorScheme,
flashBetBackgroundColor = Color(0xFFD32F2F),
flashBetOutcomeBackground = Color.White
),
// Insights colors
insightsPrematchColorPalette = InsightsColorPalette.InsightsPrematchColors(
colorScheme = customColorScheme,
insightsButton = Color(0xFF1976D2)
),
insightsLiveColorPalette = InsightsColorPalette.InsightsLiveColors(
colorScheme = customColorScheme,
insightsButton = Color(0xFFD32F2F)
)
)
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
uiSettings = defaultUISettings(
themeSettings = themeSettings
)
)Set a custom font family to match your application's branding and typography style.
Custom Font Family:
val customFont = FontFamily(
Font(R.font.my_custom_regular, FontWeight.Normal),
Font(R.font.my_custom_bold, FontWeight.Bold)
)
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
uiSettings = defaultUISettings(
fontFamily = customFont
)
)Replace default icons with custom drawables to match your application's design system. You can customize chat icons, event icons, insights icons, and sport-specific icons.
Custom Icons:
val iconsSettings = IconsSettings(
chatIcons = ChatIcons(
send = R.drawable.ic_custom_send,
attach = R.drawable.ic_custom_attach,
// ... other chat icons
),
eventIcons = EventIcons(
goal = R.drawable.ic_custom_goal,
card = R.drawable.ic_custom_card,
// ... other event icons
),
insightsIcons = InsightsIcons(
insights = R.drawable.ic_custom_insights
),
sportIcons = { sportId ->
when (sportId) {
"1" -> R.drawable.ic_soccer
"2" -> R.drawable.ic_basketball
else -> R.drawable.ic_default_sport
}
}
)
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
uiSettings = defaultUISettings(
iconsSettings = iconsSettings
)
)Configure additional UI behavior including bet share button position, bottom sheet presentation, and mock data for testing.
enum class BetSharePosition {
TOP, // Share button at top of shared bets screen
BOTTOM // Share button at bottom (default)
}Other Settings:
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
uiSettings = defaultUISettings(
otherSettings = OtherSettings(
betSharePosition = BetSharePosition.TOP,
presentedInBottomSheet = true, // Show as bottom sheet
useMockedInsights = false // Use real insights
)
)
)Here's a comprehensive example with all features enabled:
ComprehensiveChatExample.kt:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
/**
* TODO(developer): Replace placeholder values with your actual configuration
*/
setContent {
// Custom theme
val customTheme = defaultThemeSettings(
colorScheme = myColorScheme()
)
// Flash Bet state
val flashBetState = rememberFlashBetState()
flashBetState.onFlashBetCallback = {
fetchFlashBetMarket()
}
// Bet Insights state
val betInsightsState = rememberBetInsightsState()
betInsightsState.onNewInsights = { requests ->
lifecycleScope.launch {
val payload = mapInsightsData(requests)
betInsightsState.updateInsights(payload)
}
}
betInsightsState.onInsightOutcomeClicked = { eventId, outcomeId ->
addToBetSlip(eventId, outcomeId)
}
// Match status state
val matchStatusState = rememberMatchStatusState()
Chat(
jwtToken = getJwtToken(),
channelId = getChannelId(),
languageCode = LanguageCode.EN,
modifier = Modifier.fillMaxSize(),
// UI customization
uiSettings = defaultUISettings(
themeSettings = customTheme,
fontFamily = myCustomFont(),
iconsSettings = myCustomIcons(),
otherSettings = OtherSettings(
betSharePosition = BetSharePosition.BOTTOM,
presentedInBottomSheet = true
)
),
// Bet sharing
betSlipListProvider = { getBetSlips() },
onSharedBetsClicked = { logEvent("shared_bets_opened") },
onCopyToBetSlipClicked = { bet -> copyBet(bet) },
// User interactions
onProfileClicked = { userId -> openProfile(userId) },
onTagClicked = { tag -> handleTag(tag) },
onContactSupportClicked = { reason -> openSupport(reason) },
// Analytics
analyticsProvider = MyAnalyticsProvider(),
// Feature states
flashBetState = flashBetState,
betInsightsState = betInsightsState,
matchStatusState = matchStatusState,
// Auth
onLogout = { logoutCallback ->
performLogout()
logoutCallback()
}
)
}
}
}Now that you've implemented the Chat component, explore additional features and components:
Add user profiles to display statistics, shared bets, and user activity in the community.
Learn about JWT token generation and user authentication requirements.
Customize language support and localized content for your users.
Review all required dependencies and installation instructions.