AuthyraInstance
Overview
AuthyraInstance is the singleton wrapper around AuthyraClient. It adds:
- Synchronous state — read
currentUser,currentState, andisAuthenticatedwithoutawait. - Reactive streams —
authStateChangesandsessionStreambroadcast every state change. - Convenience delegation —
signIn,signOut, andrefreshSessionforward to the client. - Multi-account API —
accountsexposesAccountManager.
Access it via the Authyra typedef (a short alias for AuthyraInstance):
import 'package:authyra/authyra.dart';
// Initialize once at startup
await Authyra.initialize(client: client);
// Use anywhere
Authyra.instance.isAuthenticated; // bool — synchronous
Initialization
Authyra.initialize()
Static factory. Creates and initialises the singleton. Must be called once before Authyra.instance.
static Future<AuthyraInstance> initialize({
required AuthyraClient client,
})
void main() async {
final client = AuthyraClient(
providers: [CredentialsProvider(id: 'email', authorize: myCallback)],
storage: InMemoryAuthStorage(),
);
await Authyra.initialize(client: client);
// Authyra.instance is now available everywhere.
}
Calling initialize a second time returns the existing instance without re-initialising. To reset (e.g., in tests), call dispose() first.
isInitialized
true after initialize() has completed.
static bool get isInitialized
Synchronous State
These getters are safe to call anywhere — including build() methods — without await.
currentUser
AuthUser? get currentUser
The currently active user, or null if not authenticated.
final user = Authyra.instance.currentUser;
if (user != null) print('Hello, ${user.name}');
currentState
AuthState get currentState
The latest AuthState. Starts as AuthState.unauthenticated() before any sign-in.
final state = Authyra.instance.currentState;
print(state.type); // AuthStateType.unauthenticated
isAuthenticated
bool get isAuthenticated
Shorthand for currentState.isAuthenticated.
if (Authyra.instance.isAuthenticated) {
// show home screen
}
Async Accessors
These reach into storage when the in-memory cache is stale.
getSession()
Future<AuthSession?> getSession()
Returns the full AuthSession for the active user, or null.
final session = await Authyra.instance.getSession();
print(session?.accessToken);
print(session?.expiresAt);
getUser()
Future<AuthUser?> getUser()
final user = await Authyra.instance.getUser();
getAccessToken()
Future<String?> getAccessToken()
Returns the raw access token string, or null when not authenticated.
final token = await Authyra.instance.getAccessToken();
myHttpClient.headers['Authorization'] = 'Bearer $token';
Reactive Streams
authStateChanges
Stream<AuthState> get authStateChanges
Broadcast stream of AuthState. Emits on every sign-in, sign-out, token refresh, and error.
Consecutive identical states are deduplicated (via Equatable) — you won't get spurious rebuilds when the state hasn't actually changed.
Authyra.instance.authStateChanges.listen((AuthState state) {
switch (state.type) {
case AuthStateType.authenticated:
print('Signed in as ${state.user!.email}');
case AuthStateType.unauthenticated:
print('Signed out');
case AuthStateType.error:
print('Auth error: ${state.error}');
}
});
GoRouter integration:
GoRouter(
refreshListenable: StreamToListenable(Authyra.instance.authStateChanges),
redirect: (context, state) {
if (!Authyra.instance.isAuthenticated) return '/login';
return null; // no redirect needed
},
routes: [ ... ],
);
Riverpod integration:
final authStateProvider = StreamProvider<AuthState>((ref) {
return Authyra.instance.authStateChanges;
});
sessionStream
Stream<AuthSession?> get sessionStream
Broadcast stream of the raw AuthSession. Emits null on sign-out.
Authyra.instance.sessionStream.listen((session) {
if (session?.isExpired ?? false) {
print('Token expired — refresh needed');
}
});
Authentication Actions
signIn()
Delegates to AuthyraClient.signIn().
Future<AuthUser> signIn(
String providerId, {
Map<String, dynamic>? params,
})
| Parameter | Type | Description |
|---|---|---|
providerId | String | The id of the provider to use. |
params | Map<String, dynamic>? | Credentials or OAuth params passed to the provider. |
Returns AuthUser on success. Throws on failure — never returns null.
try {
final user = await Authyra.instance.signIn('email', params: {
'email': 'alice@example.com',
'password': 's3cr3t',
});
print('Welcome, ${user.name}');
} on AuthenticationFailedException catch (e) {
print('Invalid credentials: $e');
} on ProviderNotFoundException catch (e) {
print('No such provider: $e');
}
signOut()
Signs out the currently active account.
Future<void> signOut()
Emits AuthState.unauthenticated() on authStateChanges after completion.
await Authyra.instance.signOut();
print(Authyra.instance.isAuthenticated); // false
refreshSession()
Silently refreshes the access token for the active session.
Future<bool> refreshSession()
Returns true on success, false if the refresh token is expired or the provider does not support refresh.
final ok = await Authyra.instance.refreshSession();
if (!ok) {
// Force re-authentication
await Authyra.instance.signOut();
}
Multi-Account
accounts
AccountManager get accounts
Exposes the AccountManager for multi-session operations.
final mgr = Authyra.instance.accounts;
// List all signed-in users (sorted by last activity)
final users = await mgr.getAll(); // List<AuthUser>
// Switch the active account
await mgr.switchTo('user_id_here');
// Sign out a specific account
await mgr.signOut('user_id_here');
// Sign out every account
await mgr.signOutAll();
// Remove expired sessions, returns count removed
final removed = await mgr.cleanExpired();
Lifecycle
dispose()
Tears down the singleton: cancels stream subscriptions, closes controllers, and resets the internal _instance reference.
Future<void> dispose()
After dispose(), Authyra.isInitialized returns false and Authyra.instance throws until initialize() is called again. Useful in tests to reset state between test cases.
tearDown(() async {
if (Authyra.isInitialized) {
await Authyra.instance.dispose();
}
});
Complete Example
import 'package:authyra/authyra.dart';
Future<void> main() async {
// 1. Configure and initialize
await Authyra.initialize(
client: AuthyraClient(
providers: [
CredentialsProvider.withTokens(
id: 'api',
authorize: (creds) async {
final res = await myApi.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']),
);
},
),
],
storage: InMemoryAuthStorage(),
),
);
// 2. Subscribe to state changes before signing in
Authyra.instance.authStateChanges.listen((state) {
print('[auth] ${state.type.name} — user: ${state.user?.email}');
});
// 3. Sign in
final user = await Authyra.instance.signIn('api', params: {
'email': 'alice@example.com',
'password': 'secret',
});
print('Hello, ${user.name}!');
// 4. Use synchronous state in rendering logic
print('isAuthenticated: ${Authyra.instance.isAuthenticated}');
// 5. Read the access token for HTTP calls
final token = await Authyra.instance.getAccessToken();
print('Bearer $token');
// 6. Sign out
await Authyra.instance.signOut();
print('isAuthenticated: ${Authyra.instance.isAuthenticated}'); // false
// 7. Dispose (optional — mainly for tests)
await Authyra.instance.dispose();
}