This guide will walk you through implementing session management in your Swift app using the Dynamic Swift SDK. You’ll learn how to manage authentication state, handle automatic UI updates, and create a seamless user experience.

Overview

Session management is a crucial part of any Web3 app. The Dynamic Swift SDK provides powerful tools for managing user sessions, authentication state, and automatic UI updates. This guide covers the practical implementation patterns you’ll need to build a robust session management system.

Key Concepts

Session State Management

The SDK provides DynamicSessionState, an observable object that automatically tracks:
  • Authentication Status - Whether the user is logged in
  • Initialization Status - Whether the SDK is ready
  • User Information - Current user data and credentials
  • Wallet Status - Connected wallets and their states

Automatic UI Updates

By binding session state to your SwiftUI views, the UI automatically updates when:
  • Users log in or out
  • Authentication state changes
  • Wallets are connected or disconnected
  • Network connections change

Implementation Patterns

1. Basic Session Management Setup

Start with a simple session management setup:
import DynamicSwiftSDK
import SwiftUI

struct ContentView: View {
    @StateObject private var sessionState = DynamicSessionState()
    @State private var client: DynamicClient?

    var body: some View {
        Group {
            if !sessionState.isInitialized {
                LoadingView()
            } else if sessionState.isLoggedIn {
                MainAppView(client: client!)
            } else {
                LoginView(client: client!)
            }
        }
        .task {
            await initializeSDK()
        }
    }

    private func initializeSDK() async {
        let config = DynamicClientConfig(
            environmentId: ProcessInfo.processInfo.environment["DYNAMIC_ENVIRONMENT_ID"] ?? ""
        )
        
        let newClient = createDynamicClient(config: config)
        client = newClient
        
        // Bind session state for automatic UI updates
        bindSessionState(sessionState, to: newClient)
        
        // Add blockchain functionality
        do {
            try await addEthereumConnector(
                to: newClient,
                networkConfigProvider: GenericNetworkConfigurationProvider(),
                initialChainId: 84532
            )
        } catch {
            print("Failed to add Ethereum connector: \(error)")
        }
    }
}

2. Advanced Session Management with Error Handling

For production apps, implement robust error handling and state management:
import DynamicSwiftSDK
import SwiftUI

class SessionManager: ObservableObject {
    @Published var sessionState = DynamicSessionState()
    @Published var client: DynamicClient?
    @Published var initializationError: String?
    @Published var isInitializing = false
    
    func initialize() async {
        isInitializing = true
        initializationError = nil
        
        do {
            let config = DynamicClientConfig(
                environmentId: ProcessInfo.processInfo.environment["DYNAMIC_ENVIRONMENT_ID"] ?? ""
            )
            
            let newClient = createDynamicClient(config: config)
            client = newClient
            
            // Bind session state
            bindSessionState(sessionState, to: newClient)
            
            // Initialize client settings
            try await initializeClient(client: newClient)
            
            // Add blockchain connector
            try await addEthereumConnector(
                to: newClient,
                networkConfigProvider: GenericNetworkConfigurationProvider(),
                initialChainId: 84532
            )
            
        } catch {
            initializationError = error.localizedDescription
        }
        
        isInitializing = false
    }
    
    func refreshSession() async {
        guard let existingClient = client else { return }
        
        do {
            try await initializeClient(client: existingClient)
        } catch {
            print("Failed to refresh session: \(error)")
        }
    }
}

struct AppRootView: View {
    @StateObject private var sessionManager = SessionManager()
    
    var body: some View {
        Group {
            if sessionManager.isInitializing {
                InitializingView()
            } else if let error = sessionManager.initializationError {
                ErrorView(error: error) {
                    Task {
                        await sessionManager.initialize()
                    }
                }
            } else if sessionManager.sessionState.isInitialized {
                if sessionManager.sessionState.isLoggedIn {
                    MainAppView(client: sessionManager.client!)
                } else {
                    LoginView(client: sessionManager.client!)
                }
            } else {
                LoadingView()
            }
        }
        .task {
            await sessionManager.initialize()
        }
        .onReceive(
            NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
        ) { _ in
            Task {
                await sessionManager.refreshSession()
            }
        }
    }
}

3. Authentication State Management

Create dedicated views for different authentication states:
struct LoadingView: View {
    var body: some View {
        VStack(spacing: 20) {
            ProgressView()
                .scaleEffect(1.5)
            Text("Initializing...")
                .font(.headline)
                .foregroundColor(.secondary)
        }
    }
}

struct InitializingView: View {
    var body: some View {
        VStack(spacing: 20) {
            ProgressView()
                .scaleEffect(1.5)
                .tint(.blue)
            Text("Setting up your wallet...")
                .font(.headline)
                .foregroundColor(.secondary)
        }
    }
}

struct ErrorView: View {
    let error: String
    let retryAction: () -> Void
    
    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: "exclamationmark.triangle")
                .font(.system(size: 50))
                .foregroundColor(.orange)
            
            Text("Initialization Failed")
                .font(.title2)
                .fontWeight(.semibold)
            
            Text(error)
                .font(.body)
                .foregroundColor(.secondary)
                .multilineTextAlignment(.center)
                .padding(.horizontal)
            
            Button("Try Again") {
                retryAction()
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

4. Session State Observers

Create custom observers for specific session events:
class SessionObserver: ObservableObject {
    @Published var userProfile: UserProfile?
    @Published var connectedWallets: [Wallet] = []
    @Published var isWalletConnected = false
    
    private var client: DynamicClient
    private var cancellables = Set<AnyCancellable>()
    
    init(client: DynamicClient) {
        self.client = client
        setupObservers()
    }
    
    private func setupObservers() {
        // Observe user changes
        client.$user
            .compactMap { $0 }
            .sink { [weak self] user in
                self?.userProfile = UserProfile(from: user)
            }
            .store(in: &cancellables)
        
        // Observe wallet connections
        client.$wallets
            .sink { [weak self] wallets in
                self?.connectedWallets = wallets
                self?.isWalletConnected = !wallets.isEmpty
            }
            .store(in: &cancellables)
    }
}

struct UserProfile {
    let id: String
    let email: String?
    let phoneNumber: String?
    let verifiedCredentials: [JwtVerifiedCredential]?
    
    init(from user: SdkUser) {
        self.id = user.id
        self.email = user.email
        self.phoneNumber = user.phoneNumber
        self.verifiedCredentials = user.verifiedCredentials
    }
}

Best Practices

1. Initialization Order

Always follow this initialization order:
// 1. Create client
let client = createDynamicClient(config: config)

// 2. Bind session state
bindSessionState(sessionState, to: client)

// 3. Initialize client settings
try await initializeClient(client: client)

// 4. Add blockchain connectors
try await addEthereumConnector(to: client, ...)

2. Error Handling

Implement comprehensive error handling:
enum SessionError: Error, LocalizedError {
    case initializationFailed(String)
    case networkError
    case invalidEnvironmentId
    
    var errorDescription: String? {
        switch self {
        case .initializationFailed(let message):
            return "Initialization failed: \(message)"
        case .networkError:
            return "Network connection error"
        case .invalidEnvironmentId:
            return "Invalid environment configuration"
        }
    }
}

func handleSessionError(_ error: Error) {
    if let sessionError = error as? SessionError {
        // Handle specific session errors
        showError(sessionError.localizedDescription)
    } else {
        // Handle generic errors
        showError("An unexpected error occurred")
    }
}

3. State Persistence

Consider implementing state persistence for better user experience:
class PersistentSessionManager: ObservableObject {
    @Published var sessionState = DynamicSessionState()
    @Published var client: DynamicClient?
    
    private let userDefaults = UserDefaults.standard
    private let sessionKey = "dynamic_session_data"
    
    func saveSessionData() {
        // Save minimal session data for app restart
        let sessionData = SessionData(
            environmentId: ProcessInfo.processInfo.environment["DYNAMIC_ENVIRONMENT_ID"] ?? "",
            lastLoginDate: Date()
        )
        
        if let encoded = try? JSONEncoder().encode(sessionData) {
            userDefaults.set(encoded, forKey: sessionKey)
        }
    }
    
    func loadSessionData() -> SessionData? {
        guard let data = userDefaults.data(forKey: sessionKey),
              let sessionData = try? JSONDecoder().decode(SessionData.self, from: data) else {
            return nil
        }
        return sessionData
    }
}

struct SessionData: Codable {
    let environmentId: String
    let lastLoginDate: Date
}

4. App Lifecycle Management

Handle app lifecycle events properly:
struct AppRootView: View {
    @StateObject private var sessionManager = SessionManager()
    
    var body: some View {
        ContentView()
            .onReceive(
                NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
            ) { _ in
                Task {
                    await sessionManager.refreshSession()
                }
            }
            .onReceive(
                NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)
            ) { _ in
                sessionManager.saveSessionData()
            }
    }
}

Troubleshooting

Common Issues

Session state not updating
  • Ensure bindSessionState is called after creating the client
  • Verify your UI is observing the DynamicSessionState object
  • Check that the client is properly initialized
Initialization fails
  • Verify environment variables are set correctly
  • Check network connectivity
  • Ensure your environment ID is valid
UI not responding to state changes
  • Use @StateObject for session state in SwiftUI
  • Ensure your views are properly observing the session state
  • Check that bindings are set up correctly

Debug Session State

Add debugging to understand session state changes:
class DebugSessionManager: ObservableObject {
    @Published var sessionState = DynamicSessionState()
    
    init() {
        // Debug session state changes
        sessionState.objectWillChange.sink { [weak self] in
            print("Session state will change")
            print("Is logged in: \(self?.sessionState.isLoggedIn ?? false)")
            print("Is initialized: \(self?.sessionState.isInitialized ?? false)")
        }
        .store(in: &cancellables)
    }
}

What’s Next

Now that you have session management set up, you can:
  1. Authentication Guide - Implement user authentication flows
  2. Wallet Creation - Create and manage embedded wallets
  3. Networks - Configure blockchain networks