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: InMemoryStorage(),
);
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, {
AuthSignInParams? params,
})
| Parameter | Type | Description |
|---|---|---|
providerId | String | The id of the provider to use. |
params | AuthSignInParams? | Typed params for the provider: CredentialsSignInParams, OAuth2SignInParams, or your own subclass. |
Returns AuthUser on success. Throws on failure, never returns null.
try {
final user = await Authyra.instance.signIn('email', params: CredentialsSignInParams(
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: InMemoryStorage(),
),
);
// 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: CredentialsSignInParams(
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();
}