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.
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:
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:
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:
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:
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:
// 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
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
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 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.