The Profile component displays user information, statistics, and betting activity within the Virtual Stadium community. It shows a user's shared bets, copy count, and engagement metrics, and is typically accessed through the Chat component's user interactions.
The Profile component provides a detailed view of a user's activity in the Virtual Stadium community:
This guide will walk you through each step with code examples and best practices.
Prerequisites
VirtualStadiumUISDK.init()The Profile component is independent from the Chat component. It can be displayed by only providing jwtToken and userId parameters.
Required Parameters:
jwtToken String required
JWT token required to authenticate and initialize the UI SDK. This should be the same token used for the Chat component.
userId String required
The unique identifier of the user whose profile to display. This is typically provided by the Chat component's onProfileClicked callback.
Optional Parameters:
modifier Modifier optional
Compose modifier for layout customization. Use this to control size, padding, and other layout properties. Default: Modifier
analyticsProvider AnalyticsProvider optional
Provider for analytics event tracking. Used to log user interactions and profile view events. Should match the Chat component's analytics provider for consistent tracking. Default: AnalyticsProvider()
uiSettings UISettings optional
Customization settings for theming, fonts, and icons. Should match the Chat component settings for visual consistency across your application. Default: defaultUISettings()
onClosedClicked (() -> Unit)? optional
Callback invoked when the user clicks the close button on the profile screen. Use this to handle navigation back to the chat or dismiss the profile overlay. If null, the close button will not be shown.
onCopyToBetSlip ((BetPayload) -> Unit)? optional
Callback invoked when the user copies a shared bet from the profile to their bet slip. Receives the BetPayload containing the bet details. Use this to integrate with your betting system.
onTagClicked ((Tag) -> Unit)? optional
Callback invoked when the user clicks on a tag within bet shares on the profile. Receives the Tag object that was clicked. Use this to handle tag filtering or navigation.
The Profile component is designed to overlay the Chat component. Use state management to toggle its visibility based on user interactions.
MainActivity.kt:
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import ag.sportradar.virtualstadium.uisdk.compose.Chat
import ag.sportradar.virtualstadium.uisdk.compose.Profile
import ag.sportradar.virtualstadium.uisdk.LanguageCode
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ChatScreen()
}
}
}
@Composable
fun ChatScreen() {
var showProfile by remember { mutableStateOf(false) }
var selectedUserId by remember { mutableStateOf("") }
Box(Modifier.fillMaxSize()) {
// Chat component
/**
* TODO(developer): Replace <your-jwt-token> with your actual JWT token
* and <your-channel-id> with your actual channel ID before running this code.
*/
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
modifier = Modifier.fillMaxSize(),
onProfileClicked = { userId ->
selectedUserId = userId
showProfile = true
}
)
// Profile overlay
if (showProfile) {
Profile(
modifier = Modifier.fillMaxSize(),
jwtToken = "<your-jwt-token>",
userId = selectedUserId,
onClosedClicked = {
showProfile = false
selectedUserId = ""
}
)
}
}
}Add the Profile component using traditional Android Views with XML layout. The component is initialized programmatically when a user profile is selected.
Required Parameters:
jwtToken String required
JWT token required to authenticate and initialize the UI SDK.
userId String required
The unique identifier of the user whose profile to display.
Optional Parameters:
analyticsProvider AnalyticsProvider optional
Provider for analytics event tracking.
uiSettings UISettings optional
Customization settings for theming, fonts, and icons. Should match the Chat component settings.
onClosedClicked (() -> Unit)? optional
Callback invoked when the user closes the profile screen.
onCopyToBetSlip ((BetPayload) -> Unit)? optional
Callback invoked when the user copies a bet from the profile.
onTagClicked ((Tag) -> Unit)? optional
Callback invoked when the user clicks on a tag.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ag.sportradar.virtualstadium.uisdk.views.VirtualStadiumChatView
android:id="@+id/chat_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ag.sportradar.virtualstadium.uisdk.views.VirtualStadiumProfileView
android:id="@+id/profile_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>The Profile component displays the following information:
| Metric | Description |
|---|---|
| Bets Shared | Total number of bet slips the user has shared with the community |
| Bets Copied | Number of times other users have copied this user's bets |
For a cohesive user experience, use the same uiSettings for both Chat and Profile components. This ensures consistent colors, fonts, and icons throughout your application.
Create a single UISettings instance and reuse it across both components. This maintains visual consistency and simplifies theme management.
Best Practice:
Using different uiSettings for Chat and Profile will result in an inconsistent user experience. Always share the same theme configuration.
Shared Theme Example:
import ag.sportradar.virtualstadium.uisdk.compose.*
import androidx.compose.ui.graphics.Color
val customTheme = defaultThemeSettings(
colorScheme = myCustomColorScheme(),
loadingIndicatorColor = Color(0xFF1976D2),
// ... other theme settings
)
val sharedUISettings = defaultUISettings(
themeSettings = customTheme,
fontFamily = myCustomFont(),
iconsSettings = myCustomIcons()
)
@Composable
fun ChatScreen() {
var showProfile by remember { mutableStateOf(false) }
var selectedUserId by remember { mutableStateOf("") }
Box(Modifier.fillMaxSize()) {
Chat(
jwtToken = "<jwt-token>",
channelId = "<channel-id>",
languageCode = LanguageCode.EN,
uiSettings = sharedUISettings, // Shared settings
onProfileClicked = { userId ->
selectedUserId = userId
showProfile = true
}
)
if (showProfile) {
Profile(
jwtToken = "<jwt-token>",
userId = selectedUserId,
uiSettings = sharedUISettings, // Same settings
onClosedClicked = {
showProfile = false
}
)
}
}
}Integrate with Jetpack Navigation for proper back stack management and deep linking support. This approach provides better control over navigation and supports the Android back button.
Benefits:
Navigation Integration:
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.NavType
import androidx.navigation.navArgument
@Composable
fun ChatNavHost() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "chat"
) {
composable("chat") {
Chat(
jwtToken = "<jwt-token>",
channelId = "<channel-id>",
languageCode = LanguageCode.EN,
modifier = Modifier.fillMaxSize(),
onProfileClicked = { userId ->
navController.navigate("profile/$userId")
}
)
}
composable(
route = "profile/{userId}",
arguments = listOf(
navArgument("userId") {
type = NavType.StringType
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId") ?: ""
Profile(
jwtToken = "<jwt-token>",
userId = userId,
modifier = Modifier.fillMaxSize(),
onClosedClicked = {
navController.popBackStack()
}
)
}
}
}Display the profile in a modal bottom sheet for a more native mobile experience. This pattern is ideal when you want to maintain visibility of the chat in the background.
Use Cases:
Bottom Sheet Profile:
import androidx.compose.material3.*
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChatWithBottomSheetProfile() {
val sheetState = rememberModalBottomSheetState()
var showProfile by remember { mutableStateOf(false) }
var selectedUserId by remember { mutableStateOf("") }
Box(Modifier.fillMaxSize()) {
Chat(
jwtToken = "<jwt-token>",
channelId = "<channel-id>",
languageCode = LanguageCode.EN,
modifier = Modifier.fillMaxSize(),
onProfileClicked = { userId ->
selectedUserId = userId
showProfile = true
}
)
if (showProfile) {
ModalBottomSheet(
onDismissRequest = { showProfile = false },
sheetState = sheetState
) {
Profile(
jwtToken = "<jwt-token>",
userId = selectedUserId,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.9f),
onClosedClicked = {
showProfile = false
}
)
}
}
}
}Here's a full implementation combining all best practices:
Includes:
This example demonstrates production-ready code with proper architecture and error handling.
CompleteProfileExample.kt:
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import ag.sportradar.virtualstadium.uisdk.compose.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class ChatViewModel : ViewModel() {
private val _showProfile = MutableStateFlow(false)
val showProfile: StateFlow<Boolean> = _showProfile
private val _selectedUserId = MutableStateFlow("")
val selectedUserId: StateFlow<String> = _selectedUserId
fun showProfile(userId: String) {
if (userId.isNotBlank()) {
_selectedUserId.value = userId
_showProfile.value = true
}
}
fun hideProfile() {
_showProfile.value = false
_selectedUserId.value = ""
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Enable edge-to-edge display
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ChatScreen()
}
}
}
@Composable
fun ChatScreen(viewModel: ChatViewModel = viewModel()) {
val showProfile by viewModel.showProfile.collectAsState()
val selectedUserId by viewModel.selectedUserId.collectAsState()
// Shared UI settings for consistency
val sharedUISettings = remember {
defaultUISettings(
themeSettings = defaultThemeSettings(
colorScheme = myCustomColorScheme()
),
fontFamily = myCustomFont(),
iconsSettings = myCustomIcons()
)
}
Box(Modifier.fillMaxSize()) {
// Main chat component
/**
* TODO(developer): Replace <your-jwt-token> with your actual JWT token
* and <your-channel-id> with your actual channel ID before running this code.
*/
Chat(
jwtToken = "<your-jwt-token>",
channelId = "<your-channel-id>",
languageCode = LanguageCode.EN,
modifier = Modifier.fillMaxSize(),
uiSettings = sharedUISettings,
// Bet sharing callbacks
betSlipListProvider = { getBetSlips() },
onCopyToBetSlipClicked = { bet -> copyBet(bet) },
// Profile integration with validation
onProfileClicked = { userId ->
viewModel.showProfile(userId)
}
)
// Animated profile overlay
AnimatedVisibility(
visible = showProfile,
enter = slideInHorizontally(
initialOffsetX = { it },
animationSpec = tween(300)
) + fadeIn(),
exit = slideOutHorizontally(
targetOffsetX = { it },
animationSpec = tween(300)
) + fadeOut()
) {
Profile(
jwtToken = "<your-jwt-token>",
userId = selectedUserId,
modifier = Modifier.fillMaxSize(),
uiSettings = sharedUISettings,
onClosedClicked = {
viewModel.hideProfile()
},
onCopyToBetSlip = { bet -> copyBet(bet) },
onTagClicked = { tag -> handleTagClick(tag) }
)
}
}
}