> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Session Management

Manage authentication state and wallet updates with Kotlin Flow.

## State Flows

The SDK provides flows that emit updates:

* `authenticatedUserChanges` - User login/logout
* `tokenChanges` - Auth token updates
* `userWalletsChanges` - Wallet changes
* `isAuthenticatedFlow` - Auth status

Your UI updates automatically when you collect these flows.

## Implementation Patterns

### 1. Basic Session Management

Start with a simple session management setup using a ViewModel:

```kotlin theme={"system"}
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

```kotlin theme={"system"}
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:

```kotlin theme={"system"}
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

```kotlin theme={"system"}
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:

```kotlin theme={"system"}
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:

```kotlin theme={"system"}
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:

```kotlin theme={"system"}
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:

```kotlin theme={"system"}
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:

```kotlin theme={"system"}
// 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.launch` for coroutine collection
* Verify flows are collected in the correct scope
* Check that your ViewModel is using `StateFlow` and `collectAsState()` properly

**Navigation not working after login**

* Make sure you're subscribed to `authenticatedUserChanges` before the user authenticates
* Check that your navigation logic handles the case where user is already authenticated

**Wallets not appearing**

* Wallets are created asynchronously after authentication
* Collect from `userWalletsChanges` to 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:

```kotlin theme={"system"}
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:

1. **[Authentication Guide](/kotlin/authentication)** - Implement user authentication flows
2. **[Wallet Operations](/kotlin/wallets/general/token-balances)** - Work with wallet balances and signing
3. **[EVM Operations](/kotlin/wallets/evm/send-eth)** - Perform EVM transactions
4. **[Solana Operations](/kotlin/wallets/solana/connection)** - Send Solana transactions
