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

FeatureWhere
Email/password with JWT tokensCredentialsProvider.withTokens in main.dart
Google OAuth2 with PKCEGoogleProvider + OAuth2CallbackHandler in main.dart
Encrypted session storageSecureAuthStorage
GoRouter reactive redirectrefreshListenable + redirect in router.dart
Error handling per provideron AuthenticationFailedException in login_page.dart
Synchronous state in build()Authyra.instance.currentUser in home_page.dart

Next steps

Copyright © 2026