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

# Go Router Integration

The Dynamic Flutter SDK integrates seamlessly with `go_router` for navigation and route protection based on authentication state. This guide demonstrates how to set up go\_router with Dynamic authentication flows.

## Overview

The integration involves creating an `AuthNotifier` that bridges Dynamic SDK streams into a `ChangeNotifier` that go\_router can use to automatically handle route redirects based on authentication state.

## Complete Implementation

### 1. AuthNotifier Setup

First, create an `AuthNotifier` class that listens to Dynamic SDK authentication streams:

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

/// Simple enum to reason about routing
enum AuthPhase { loading, unauthenticated, authenticated }

/// Bridges DynamicSDK streams into a ChangeNotifier for go_router
class AuthNotifier extends ChangeNotifier {
  AuthPhase _phase = AuthPhase.loading;
  AuthPhase get phase => _phase;

  StreamSubscription<bool>? _readySub;
  StreamSubscription<dynamic /*User?*/>? _userSub;

  bool _ready = false;
  bool _hasUser = false;

  AuthNotifier() {
    // Listen to SDK readiness
    _readySub = DynamicSDK.instance.sdk.readyChanges.listen((ready) {
      _ready = ready;
      _recomputePhase();
    });

    // Listen to auth state changes
    _userSub = DynamicSDK.instance.auth.authenticatedUserChanges.listen((user) {
      _hasUser = user != null;
      _recomputePhase();
    });
  }

  void _recomputePhase() {
    final next = !_ready
        ? AuthPhase.loading
        : (_hasUser ? AuthPhase.authenticated : AuthPhase.unauthenticated);

    if (next != _phase) {
      _phase = next;
      notifyListeners();
    }
  }

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

### 2. Go Router Configuration

Set up your go\_router with the `AuthNotifier` and route protection:

```dart theme={"system"}
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late final AuthNotifier _auth;
  late final GoRouter _router;

  @override
  void initState() {
    super.initState();
    _auth = AuthNotifier();

    _router = GoRouter(
      // This makes go_router re-check redirect whenever auth/ready changes
      refreshListenable: _auth,
      routes: [
        GoRoute(path: '/', builder: (_, __) => const HomeView()),
        GoRoute(path: '/login', builder: (_, __) => const LoginView()),
        GoRoute(path: '/loading', builder: (_, __) => const LoadingView()),
        GoRoute(
          path: '/wallet/:walletId',
          builder: (_, state) =>
              WalletView(walletId: state.pathParameters['walletId']!),
        ),
      ],
      redirect: (context, state) {
        final inLogin = state.matchedLocation == '/login';
        final inLoading = state.matchedLocation == '/loading';

        switch (_auth.phase) {
          case AuthPhase.loading:
            // While SDK/user are loading, keep users on /loading
            return inLoading ? null : '/loading';

          case AuthPhase.unauthenticated:
            // If not logged in, force /login except when already there
            return inLogin ? null : '/login';

          case AuthPhase.authenticated:
            // If logged in, keep them off /login and /loading
            if (inLogin || inLoading) return '/';
            return null;
        }
      },
    );
  }

  @override
  void dispose() {
    _auth.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter Demo',
      routerDelegate: _router.routerDelegate,
      routeInformationParser: _router.routeInformationParser,
      routeInformationProvider: _router.routeInformationProvider,
      builder: (context, child) {
        if (child == null) return const SizedBox.shrink();

        return Stack(
          children: [
            child,
            // Keep Dynamic overlay/widget above your pages
            DynamicSDK.instance.dynamicWidget,
          ],
        );
      },
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
    );
  }
}
```

### 3. Dynamic SDK Initialization

Initialize the Dynamic SDK in your `main()` function:

```dart theme={"system"}
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  DynamicSDK.init(
    props: ClientProps(
      environmentId: 'your-environment-id',
      appLogoUrl: 'https://your-app.com/logo.png',
      appName: 'Your App Name',
      redirectUrl: "yourapp://",
    ),
  );
  runApp(const MyApp());
}
```

## Route Protection Patterns

### Authentication States

The `AuthNotifier` manages three distinct states:

* **`AuthPhase.loading`**: SDK is initializing or user state is being determined
* **`AuthPhase.unauthenticated`**: SDK is ready but no user is authenticated
* **`AuthPhase.authenticated`**: SDK is ready and user is authenticated

### Redirect Logic

The redirect function automatically handles navigation based on authentication state:

```dart theme={"system"}
redirect: (context, state) {
  final inLogin = state.matchedLocation == '/login';
  final inLoading = state.matchedLocation == '/loading';

  switch (_auth.phase) {
    case AuthPhase.loading:
      return inLoading ? null : '/loading';

    case AuthPhase.unauthenticated:
      return inLogin ? null : '/login';

    case AuthPhase.authenticated:
      if (inLogin || inLoading) return '/';
      return null;
  }
}
```

### Navigation Examples

Navigate programmatically using go\_router methods:

```dart theme={"system"}
// Navigate to a specific wallet
context.push('/wallet/${wallet.id}');

// Navigate to home after logout
context.go('/');

// Navigate with parameters
context.pushNamed('wallet', pathParameters: {'walletId': walletId});
```

## Integration with Dynamic UI

### Login View

```dart theme={"system"}
class LoginView extends StatelessWidget {
  const LoginView({super.key});

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

### Loading View

```dart theme={"system"}
class LoadingView extends StatelessWidget {
  const LoadingView({super.key});

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

## Live Example

For a complete working example, check out the [Dynamic Flutter Example App](https://github.com/dynamic-labs/flutter-example-app) which demonstrates this exact integration pattern with additional features like wallet management and web3dart integration.

You can read more about go\_router in the [official documentation](https://pub.dev/packages/go_router).
