State Flows
The SDK provides flows that emit updates:authenticatedUserChanges- User login/logouttokenChanges- Auth token updatesuserWalletsChanges- Wallet changesisAuthenticatedFlow- Auth status
Implementation Patterns
1. Basic Session Management
Start with a simple session management setup using a ViewModel:Copy
Ask AI
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import com.dynamic.sdk.android.Models.UserProfile
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class SessionViewModel : ViewModel() {
private val sdk = DynamicSDK.getInstance()
private val _isAuthenticated = MutableStateFlow(false)
val isAuthenticated: StateFlow<Boolean> = _isAuthenticated.asStateFlow()
private val _isLoading = MutableStateFlow(true)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _user = MutableStateFlow<UserProfile?>(null)
val user: StateFlow<UserProfile?> = _user.asStateFlow()
private val _wallets = MutableStateFlow<List<BaseWallet>>(emptyList())
val wallets: StateFlow<List<BaseWallet>> = _wallets.asStateFlow()
init {
setupObservers()
}
private fun setupObservers() {
// Observe authentication state
viewModelScope.launch {
sdk.auth.authenticatedUserChanges.collect { user ->
_isAuthenticated.value = user != null
_user.value = user
_isLoading.value = false
}
}
// Observe wallet changes
viewModelScope.launch {
sdk.wallets.userWalletsChanges.collect { wallets ->
_wallets.value = wallets
}
}
}
}
2. Using Session Manager in Jetpack Compose
Copy
Ask AI
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.UI.DynamicUI
import com.dynamic.sdk.android.core.ClientProps
import com.dynamic.sdk.android.core.LoggerLevel
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize SDK at app launch
val props = ClientProps(
environmentId = "your-environment-id",
appLogoUrl = "https://your-app.com/logo.png",
appName = "Your App",
redirectUrl = "yourapp://",
appOrigin = "https://your-app.com",
logLevel = LoggerLevel.DEBUG
)
DynamicSDK.initialize(props, applicationContext, this)
setContent {
MaterialTheme {
Surface(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize()) {
AppContent()
DynamicUI()
}
}
}
}
}
}
@Composable
fun AppContent() {
val viewModel: SessionViewModel = viewModel()
val isLoading by viewModel.isLoading.collectAsState()
val isAuthenticated by viewModel.isAuthenticated.collectAsState()
when {
isLoading -> LoadingScreen()
isAuthenticated -> MainAppScreen(viewModel)
else -> LoginScreen()
}
}
@Composable
fun LoadingScreen() {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center) {
CircularProgressIndicator()
}
}
3. Complete Session Manager
For production apps, implement a comprehensive session manager:Copy
Ask AI
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import com.dynamic.sdk.android.Models.UserProfile
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class SessionManager : ViewModel() {
private val sdk = DynamicSDK.getInstance()
private val _isAuthenticated = MutableStateFlow(false)
val isAuthenticated: StateFlow<Boolean> = _isAuthenticated.asStateFlow()
private val _user = MutableStateFlow<UserProfile?>(null)
val user: StateFlow<UserProfile?> = _user.asStateFlow()
private val _wallets = MutableStateFlow<List<BaseWallet>>(emptyList())
val wallets: StateFlow<List<BaseWallet>> = _wallets.asStateFlow()
private val _token = MutableStateFlow<String?>(null)
val token: StateFlow<String?> = _token.asStateFlow()
private val _isCreatingWallets = MutableStateFlow(false)
val isCreatingWallets: StateFlow<Boolean> = _isCreatingWallets.asStateFlow()
private val _errorMessage = MutableStateFlow<String?>(null)
val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow()
init {
setupObservers()
}
private fun setupObservers() {
// Initial values
_isAuthenticated.value = sdk.auth.isAuthenticated()
_user.value = sdk.auth.authenticatedUser
_wallets.value = sdk.wallets.userWallets
_token.value = sdk.auth.token
// Check if wallets are being created
if (_user.value != null && _wallets.value.isEmpty()) {
_isCreatingWallets.value = true
}
// Observe authentication state
viewModelScope.launch {
sdk.auth.authenticatedUserChanges.collect { user ->
_isAuthenticated.value = user != null
_user.value = user
if (user == null) {
// User logged out
_wallets.value = emptyList()
_isCreatingWallets.value = false
} else if (_wallets.value.isEmpty()) {
// User just authenticated, wallets being created
_isCreatingWallets.value = true
}
}
}
// Observe wallet changes
viewModelScope.launch {
sdk.wallets.userWalletsChanges.collect { wallets ->
_wallets.value = wallets
// Wallets appeared, stop showing loading
if (wallets.isNotEmpty()) {
_isCreatingWallets.value = false
}
}
}
// Observe token changes
viewModelScope.launch {
sdk.auth.tokenChanges.collect { token ->
_token.value = token
}
}
}
fun logout() {
viewModelScope.launch {
try {
sdk.auth.logout()
} catch (e: Exception) {
_errorMessage.value = "Logout failed: ${e.message}"
}
}
}
fun showUserProfile() {
sdk.ui.showUserProfile()
}
}
4. Home Screen with Session Management
Copy
Ask AI
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.dynamic.sdk.android.Models.BaseWallet
@Composable
fun MainAppScreen(sessionManager: SessionManager) {
val user by sessionManager.user.collectAsState()
val wallets by sessionManager.wallets.collectAsState()
val isCreatingWallets by sessionManager.isCreatingWallets.collectAsState()
val errorMessage by sessionManager.errorMessage.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// User info
user?.let { userProfile ->
Text(
text = "Welcome, ${userProfile.email ?: "User"}!",
style = MaterialTheme.typography.headlineMedium
)
}
Spacer(modifier = Modifier.height(16.dp))
// Wallets section
when {
isCreatingWallets -> {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
Text("Creating wallets...")
}
}
wallets.isEmpty() -> {
Text("No wallets")
}
else -> {
Text(
text = "Your Wallets",
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(wallets) { wallet ->
WalletCard(wallet)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Error message
errorMessage?.let { error ->
Text(
text = error,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall
)
}
Spacer(modifier = Modifier.height(16.dp))
// Actions
Button(
onClick = { sessionManager.showUserProfile() },
modifier = Modifier.fillMaxWidth()
) {
Text("Show Profile")
}
Button(
onClick = { sessionManager.logout() },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text("Logout")
}
}
}
@Composable
fun WalletCard(wallet: BaseWallet) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = wallet.chain.uppercase(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary
)
Text(
text = wallet.address,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1
)
}
}
}
5. Navigation Based on Auth State
Handle navigation when authentication state changes:Copy
Ask AI
import androidx.compose.runtime.*
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.dynamic.sdk.android.DynamicSDK
import kotlinx.coroutines.launch
@Composable
fun NavigationRoot() {
val sdk = DynamicSDK.getInstance()
val navController = rememberNavController()
var isAuthenticated by remember { mutableStateOf(sdk.auth.isAuthenticated()) }
// Listen for auth changes
LaunchedEffect(Unit) {
sdk.auth.authenticatedUserChanges.collect { user ->
isAuthenticated = user != null
if (user != null) {
navController.navigate("home") {
popUpTo("login") { inclusive = true }
}
} else {
navController.navigate("login") {
popUpTo("home") { inclusive = true }
}
}
}
}
NavHost(
navController = navController,
startDestination = if (isAuthenticated) "home" else "login"
) {
composable("login") {
LoginScreen()
}
composable("home") {
MainAppScreen(SessionManager())
}
}
}
Best Practices
1. Always Collect on Main Dispatcher
When updating UI state from flows, make sure to use the main dispatcher:Copy
Ask AI
viewModelScope.launch {
sdk.auth.authenticatedUserChanges.collect { user ->
// This automatically runs on Main dispatcher in ViewModelScope
_isAuthenticated.value = user != null
}
}
2. Initialization Order
Always initialize the SDK at app launch:Copy
Ask AI
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize SDK before setting content
val props = ClientProps(
environmentId = "your-env-id",
appName = "Your App",
redirectUrl = "yourapp://",
appOrigin = "https://your-app.com"
)
DynamicSDK.initialize(props, applicationContext, this)
setContent {
// Now safe to use SDK
AppContent()
}
}
}
3. Check Initial State
Always check the current state before setting up listeners:Copy
Ask AI
fun startListening() {
// Check current state first
_user.value = sdk.auth.authenticatedUser
_wallets.value = sdk.wallets.userWallets
// Then set up listeners
viewModelScope.launch {
sdk.auth.authenticatedUserChanges.collect { ... }
}
}
4. Handle Wallet Creation Loading State
Wallets are created asynchronously after authentication:Copy
Ask AI
// Show loading state when user is authenticated but wallets haven't appeared yet
if (user != null && wallets.isEmpty()) {
_isCreatingWallets.value = true
}
// Clear loading state when wallets appear
viewModelScope.launch {
sdk.wallets.userWalletsChanges.collect { newWallets ->
if (newWallets.isNotEmpty()) {
_isCreatingWallets.value = false
}
}
}
Troubleshooting
Common Issues
State not updating- Ensure you’re using
viewModelScope.launchfor coroutine collection - Verify flows are collected in the correct scope
- Check that your ViewModel is using
StateFlowandcollectAsState()properly
- Make sure you’re subscribed to
authenticatedUserChangesbefore the user authenticates - Check that your navigation logic handles the case where user is already authenticated
- Wallets are created asynchronously after authentication
- Collect from
userWalletsChangesto receive updates - Check that embedded wallets are enabled in your Dynamic dashboard
- Ensure proper Flow collection with
collectAsState()in Compose
Debug Session State
Add logging to understand state changes:Copy
Ask AI
viewModelScope.launch {
sdk.auth.authenticatedUserChanges.collect { user ->
android.util.Log.d("Session", "Auth state changed: ${user != null}")
user?.let {
android.util.Log.d("Session", "User ID: ${it.userId}")
}
}
}
viewModelScope.launch {
sdk.wallets.userWalletsChanges.collect { wallets ->
android.util.Log.d("Session", "Wallets updated: ${wallets.size} wallets")
wallets.forEach { wallet ->
android.util.Log.d("Session", " - ${wallet.chain}: ${wallet.address}")
}
}
}
What’s Next
Now that you have session management set up, you can:- Authentication Guide - Implement user authentication flows
- Wallet Operations - Work with wallet balances and signing
- EVM Operations - Perform EVM transactions
- Solana Operations - Send Solana transactions