> ## 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

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

## Overview

Session management is a crucial part of any Web3 app. The Dynamic Flutter SDK provides powerful Stream-based reactive state management for user sessions, authentication state, and wallet updates. This guide covers the practical implementation patterns you'll need to build a robust session management system.

## Key Concepts

### Reactive State with Streams

The SDK provides Dart Streams that automatically emit updates when state changes:

* **`authenticatedUserChanges`** - Emits when user logs in or out
* **`tokenChanges`** - Emits when the auth token changes
* **`userWalletsChanges`** - Emits when wallets are created or updated
* **`readyChanges`** - Emits when SDK initialization state changes

### Automatic UI Updates

By subscribing to these streams in your Flutter widgets using `StreamBuilder`, the UI automatically updates when:

* Users log in or out
* Authentication tokens refresh
* Wallets are connected or created
* SDK ready state changes

## Implementation Patterns

### 1. Basic Session Management

Start with a simple session management setup:

```dart theme={"system"}
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:flutter/material.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize SDK at app launch
  DynamicSDK.init(
    props: ClientProps(
      environmentId: 'your-environment-id',
      appLogoUrl: 'https://your-app.com/logo.png',
      appName: 'Your App',
      redirectUrl: 'yourapp://',
    ),
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dynamic App',
      home: Stack(
        children: [
          const SessionManagedApp(),
          // Dynamic SDK widget overlay
          DynamicSDK.instance.dynamicWidget,
        ],
      ),
    );
  }
}

class SessionManagedApp extends StatelessWidget {
  const SessionManagedApp({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<bool?>(
      stream: DynamicSDK.instance.sdk.readyChanges,
      builder: (context, readySnapshot) {
        final sdkReady = readySnapshot.data ?? false;

        if (!sdkReady) {
          return const LoadingView();
        }

        return StreamBuilder<String?>(
          stream: DynamicSDK.instance.auth.tokenChanges,
          builder: (context, tokenSnapshot) {
            final isAuthenticated = tokenSnapshot.data != null;

            if (isAuthenticated) {
              return const MainAppView();
            } else {
              return const LoginView();
            }
          },
        );
      },
    );
  }
}

class LoadingView extends StatelessWidget {
  const LoadingView({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
}

class LoginView extends StatelessWidget {
  const LoginView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            DynamicSDK.instance.ui.showAuth();
          },
          child: const Text('Sign In with Dynamic'),
        ),
      ),
    );
  }
}

class MainAppView extends StatelessWidget {
  const MainAppView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('My App'),
        actions: [
          IconButton(
            icon: const Icon(Icons.account_circle),
            onPressed: () {
              DynamicSDK.instance.ui.showUserProfile();
            },
          ),
        ],
      ),
      body: const Center(
        child: Text('Welcome to your app!'),
      ),
    );
  }
}
```

### 2. Advanced Session State Management

For more complex apps, create a dedicated session manager:

```dart theme={"system"}
import 'dart:async';
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:flutter/foundation.dart';

class SessionManager extends ChangeNotifier {
  SessionManager() {
    _initialize();
  }

  bool _isReady = false;
  bool _isAuthenticated = false;
  bool _isLoading = true;
  dynamic _user;
  List<BaseWallet> _wallets = [];

  StreamSubscription<bool>? _readySub;
  StreamSubscription<String?>? _tokenSub;
  StreamSubscription<dynamic>? _userSub;
  StreamSubscription<List<BaseWallet>>? _walletsSub;

  bool get isReady => _isReady;
  bool get isAuthenticated => _isAuthenticated;
  bool get isLoading => _isLoading;
  dynamic get user => _user;
  List<BaseWallet> get wallets => _wallets;

  void _initialize() {
    // Listen to SDK ready state
    _readySub = DynamicSDK.instance.sdk.readyChanges.listen((ready) {
      _isReady = ready;
      _updateLoadingState();
      notifyListeners();
    });

    // Listen to auth token changes
    _tokenSub = DynamicSDK.instance.auth.tokenChanges.listen((token) {
      _isAuthenticated = token != null;
      _updateLoadingState();
      notifyListeners();
    });

    // Listen to user changes
    _userSub = DynamicSDK.instance.auth.authenticatedUserChanges.listen((user) {
      _user = user;
      notifyListeners();
    });

    // Listen to wallet changes
    _walletsSub = DynamicSDK.instance.wallets.userWalletsChanges.listen((wallets) {
      _wallets = wallets;
      notifyListeners();
    });
  }

  void _updateLoadingState() {
    _isLoading = !_isReady;
  }

  Future<void> logout() async {
    try {
      await DynamicSDK.instance.auth.logout();
    } catch (e) {
      debugPrint('Logout error: $e');
      rethrow;
    }
  }

  @override
  void dispose() {
    _readySub?.cancel();
    _tokenSub?.cancel();
    _userSub?.cancel();
    _walletsSub?.cancel();
    super.dispose();
  }
}
```

### 3. Using Session Manager with Provider

```dart theme={"system"}
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  DynamicSDK.init(
    props: ClientProps(
      environmentId: 'your-environment-id',
      appLogoUrl: 'https://your-app.com/logo.png',
      appName: 'Your App',
    ),
  );

  runApp(
    ChangeNotifierProvider(
      create: (_) => SessionManager(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dynamic App',
      home: Stack(
        children: [
          const AppContent(),
          DynamicSDK.instance.dynamicWidget,
        ],
      ),
    );
  }
}

class AppContent extends StatelessWidget {
  const AppContent({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<SessionManager>(
      builder: (context, session, child) {
        if (session.isLoading) {
          return const LoadingView();
        }

        if (session.isAuthenticated) {
          return MainAppView(
            user: session.user,
            wallets: session.wallets,
          );
        }

        return const LoginView();
      },
    );
  }
}

class MainAppView extends StatelessWidget {
  final dynamic user;
  final List<BaseWallet> wallets;

  const MainAppView({
    super.key,
    required this.user,
    required this.wallets,
  });

  @override
  Widget build(BuildContext context) {
    final session = context.read<SessionManager>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('My App'),
        actions: [
          IconButton(
            icon: const Icon(Icons.account_circle),
            onPressed: () {
              DynamicSDK.instance.ui.showUserProfile();
            },
          ),
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () async {
              await session.logout();
            },
          ),
        ],
      ),
      body: Column(
        children: [
          if (user != null)
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Text('Welcome, ${user.email ?? "User"}!'),
            ),
          if (wallets.isNotEmpty)
            Expanded(
              child: ListView.builder(
                itemCount: wallets.length,
                itemBuilder: (context, index) {
                  final wallet = wallets[index];
                  return ListTile(
                    title: Text(wallet.address),
                    subtitle: Text('Chain: ${wallet.chain}'),
                  );
                },
              ),
            ),
        ],
      ),
    );
  }
}
```

## Listening to Specific State Changes

### Authentication Token Changes

```dart theme={"system"}
StreamBuilder<String?>(
  stream: DynamicSDK.instance.auth.tokenChanges,
  builder: (context, snapshot) {
    final token = snapshot.data;

    if (token != null) {
      // User is authenticated
      return AuthenticatedContent();
    } else {
      // User is not authenticated
      return UnauthenticatedContent();
    }
  },
)
```

### User Profile Changes

```dart theme={"system"}
StreamBuilder<dynamic>(
  stream: DynamicSDK.instance.auth.authenticatedUserChanges,
  builder: (context, snapshot) {
    final user = snapshot.data;

    if (user != null) {
      return Column(
        children: [
          Text('Email: ${user.email}'),
          Text('User ID: ${user.userId}'),
        ],
      );
    }

    return const Text('No user logged in');
  },
)
```

### Wallet Changes

```dart theme={"system"}
StreamBuilder<List<BaseWallet>>(
  stream: DynamicSDK.instance.wallets.userWalletsChanges,
  builder: (context, snapshot) {
    final wallets = snapshot.data ?? [];

    if (wallets.isEmpty) {
      return const Text('No wallets available');
    }

    return ListView.builder(
      itemCount: wallets.length,
      itemBuilder: (context, index) {
        final wallet = wallets[index];
        return WalletCard(wallet: wallet);
      },
    );
  },
)
```

### SDK Ready State

```dart theme={"system"}
StreamBuilder<bool>(
  stream: DynamicSDK.instance.sdk.readyChanges,
  builder: (context, snapshot) {
    final isReady = snapshot.data ?? false;

    if (!isReady) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    return const AppContent();
  },
)
```

## Session Persistence

The Dynamic SDK automatically persists sessions across app restarts. When your app launches:

1. Initialize the SDK in `main()`
2. Wait for `readyChanges` to emit `true`
3. Check `tokenChanges` or `authenticatedUserChanges` for existing session
4. If a valid session exists, the user is automatically authenticated

```dart theme={"system"}
void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize SDK - session restored automatically if valid
  DynamicSDK.init(
    props: ClientProps(
      environmentId: 'your-environment-id',
      appLogoUrl: 'https://your-app.com/logo.png',
      appName: 'Your App',
    ),
  );

  runApp(const MyApp());
}
```

## Manual Session Check

You can manually check the current session state:

```dart theme={"system"}
// Check if user is authenticated
final token = DynamicSDK.instance.auth.token;
final isAuthenticated = token != null;

// Get current user
final user = DynamicSDK.instance.auth.authenticatedUser;

// Get current wallets
final wallets = DynamicSDK.instance.wallets.userWallets;

if (isAuthenticated && user != null) {
  print('User ${user.userId} is authenticated');
  print('Wallets: ${wallets.length}');
}
```

## Logout

To end a user's session:

```dart theme={"system"}
Future<void> handleLogout() async {
  try {
    await DynamicSDK.instance.auth.logout();
    // Session cleared - streams will emit updates
    // UI will automatically update via StreamBuilders
  } catch (e) {
    // Handle error
    print('Logout error: $e');
  }
}
```

## Best Practices

### 1. Always Use StreamBuilders

Use `StreamBuilder` widgets to automatically update UI when session state changes:

```dart theme={"system"}
// Good ✓
StreamBuilder<String?>(
  stream: DynamicSDK.instance.auth.tokenChanges,
  builder: (context, snapshot) {
    return snapshot.data != null ? HomeView() : LoginView();
  },
)

// Bad ✗ - Won't update automatically
final token = DynamicSDK.instance.auth.token;
return token != null ? HomeView() : LoginView();
```

### 2. Include Dynamic Widget Overlay

Always include `DynamicSDK.instance.dynamicWidget` in your widget tree:

```dart theme={"system"}
Stack(
  children: [
    YourAppContent(),
    DynamicSDK.instance.dynamicWidget, // Required for auth UI
  ],
)
```

### 3. Wait for SDK Ready

Always check that the SDK is ready before using it:

```dart theme={"system"}
StreamBuilder<bool>(
  stream: DynamicSDK.instance.sdk.readyChanges,
  builder: (context, snapshot) {
    if (snapshot.data != true) {
      return LoadingView();
    }
    return AppContent();
  },
)
```

### 4. Dispose Subscriptions

If manually subscribing to streams, always cancel subscriptions:

```dart theme={"system"}
class _MyWidgetState extends State<MyWidget> {
  late StreamSubscription _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = DynamicSDK.instance.auth.tokenChanges.listen((token) {
      // Handle token changes
    });
  }

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }
}
```

## Troubleshooting

### Session Not Persisting

* Ensure SDK is initialized in `main()` before `runApp()`
* Check that `DynamicSDK.instance.dynamicWidget` is included in widget tree
* Verify you're not clearing app data or cache between sessions

### UI Not Updating

* Make sure you're using `StreamBuilder` to listen to state changes
* Check that streams are being subscribed to correctly
* Verify the widget tree is being rebuilt when state changes

### Token Expired

The SDK automatically handles token refresh. If you see authentication failures:

* Check network connectivity
* Verify your environment ID is correct
* Check dashboard settings for session duration

## What's Next

Now that you understand session management:

1. **[Wallet Creation](/flutter/wallet-creation)** - Learn about automatic wallet creation after authentication
2. **[Go Router Integration](/flutter/go-router-integration)** - Integrate session management with go\_router navigation
3. **[Token Balances](/flutter/wallets/general/token-balances)** - Display and manage wallet balances
4. **[SDK Reference](/flutter/sdk-reference/overview)** - Explore the complete SDK API

## Reference

For more details on session management APIs, see:

* [SDK Reference - Authentication](/flutter/sdk-reference/authentication)
