Examples
Flutter App Example
A complete Flutter app with email/password and Google Sign-In using authyra_flutter.
A complete Flutter app demonstrating email/password login and Google Sign-In. All auth state is managed by Authyra; the UI reacts to authStateChanges.
Setup
pubspec.yaml
dependencies:
authyra_flutter: ^0.1.0
app_links: ^6.0.0
go_router: ^14.0.0
Entry point
lib/main.dart
import 'package:app_links/app_links.dart';
import 'package:authyra_flutter/authyra_flutter.dart';
import 'package:flutter/material.dart';
import 'router.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Build providers
final googleProvider = GoogleProvider(
clientId: 'YOUR_GOOGLE_CLIENT_ID',
// redirectUri defaults to com.googleusercontent.apps.<clientId>:/oauth2redirect
);
// 2. Wire deep-link callback before Authyra.initialize
OAuth2CallbackHandler.registerProvider(
'com.googleusercontent.apps.YOUR_GOOGLE_CLIENT_ID',
googleProvider,
);
AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);
// 3. Initialize — restores any persisted session
await Authyra.initialize(
client: AuthyraClient(
providers: [
CredentialsProvider.withTokens(
id: 'email',
authorize: (creds) async {
final res = await Api.post('/auth/login', body: creds);
if (res.statusCode != 200) return null;
return AuthSignInResult(
user: AuthUser(id: res.data['id'], email: res.data['email']),
accessToken: res.data['accessToken'],
refreshToken: res.data['refreshToken'],
expiresAt: DateTime.parse(res.data['expiresAt']),
);
},
),
googleProvider,
],
storage: SecureAuthStorage(),
config: const AuthConfig(autoRefresh: true),
),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(routerConfig: router);
}
}
Router
lib/router.dart
import 'package:authyra_flutter/authyra_flutter.dart';
import 'package:go_router/go_router.dart';
import 'pages/home_page.dart';
import 'pages/login_page.dart';
/// Minimal Listenable adapter for go_router's refreshListenable.
class _StreamListenable extends ChangeNotifier {
_StreamListenable(Stream<dynamic> stream) {
stream.listen((_) => notifyListeners());
}
}
final router = GoRouter(
refreshListenable: _StreamListenable(Authyra.instance.authStateChanges),
redirect: (context, state) {
final authenticated = Authyra.instance.isAuthenticated;
final goingToLogin = state.uri.path == '/login';
if (!authenticated && !goingToLogin) return '/login';
if (authenticated && goingToLogin) return '/';
return null;
},
routes: [
GoRoute(path: '/login', builder: (_, __) => const LoginPage()),
GoRoute(path: '/', builder: (_, __) => const HomePage()),
],
);
Login page
lib/pages/login_page.dart
import 'package:authyra_flutter/authyra_flutter.dart';
import 'package:flutter/material.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _emailCtrl = TextEditingController();
final _passwordCtrl = TextEditingController();
bool _loading = false;
String? _error;
Future<void> _signInWithEmail() async {
_setLoading(true);
try {
await Authyra.instance.signIn('email', params: {
'email': _emailCtrl.text.trim(),
'password': _passwordCtrl.text,
});
// GoRouter redirects to '/' automatically via refreshListenable
} on AuthenticationFailedException {
_setError('Incorrect email or password.');
} catch (_) {
_setError('Something went wrong. Please try again.');
} finally {
_setLoading(false);
}
}
Future<void> _signInWithGoogle() async {
_setLoading(true);
try {
await Authyra.instance.signIn('google');
} on AuthenticationCancelledException {
// user closed the browser — no error to show
} on AuthenticationFailedException catch (e) {
_setError('Google sign-in failed: $e');
} finally {
_setLoading(false);
}
}
void _setLoading(bool v) => setState(() { _loading = v; _error = null; });
void _setError(String msg) => setState(() => _error = msg);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Sign in',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
const SizedBox(height: 32),
TextField(
controller: _emailCtrl,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 12),
TextField(
controller: _passwordCtrl,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
obscureText: true,
),
if (_error != null) ...[
const SizedBox(height: 12),
Text(_error!,
style: const TextStyle(color: Colors.red)),
],
const SizedBox(height: 24),
FilledButton(
onPressed: _loading ? null : _signInWithEmail,
child: _loading
? const SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Continue with email'),
),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: _loading ? null : _signInWithGoogle,
icon: const Icon(Icons.g_mobiledata),
label: const Text('Continue with Google'),
),
],
),
),
),
);
}
}
Home page
lib/pages/home_page.dart
import 'package:authyra_flutter/authyra_flutter.dart';
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
// currentUser is synchronous — safe in build()
final user = Authyra.instance.currentUser!;
return Scaffold(
appBar: AppBar(
title: Text('Hello, ${user.name ?? user.email ?? 'there'}!'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
tooltip: 'Sign out',
onPressed: () => Authyra.instance.signOut(),
),
],
),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (user.avatarUrl != null)
CircleAvatar(
radius: 36,
backgroundImage: NetworkImage(user.avatarUrl!),
),
const SizedBox(height: 16),
Text('ID: ${user.id}'),
if (user.email != null) Text('Email: ${user.email}'),
],
),
),
);
}
}
What this demonstrates
| Feature | Where |
|---|---|
| Email/password with JWT tokens | CredentialsProvider.withTokens in main.dart |
| Google OAuth2 with PKCE | GoogleProvider + OAuth2CallbackHandler in main.dart |
| Encrypted session storage | SecureAuthStorage |
| GoRouter reactive redirect | refreshListenable + redirect in router.dart |
| Error handling per provider | on AuthenticationFailedException in login_page.dart |
Synchronous state in build() | Authyra.instance.currentUser in home_page.dart |
Next steps
- Flutter Setup → — deep-link wiring details
- Google Provider → — scopes, redirect URI, token rotation
- Multi-Account → — account switching
- Route Protection → — guard patterns