Files
go_router_riverpod/example/lib/state/auth_controller.dart
Luis Pulido a85558e829 upgraded example projects
- latest packages
- latest android and ios templates
- added web
2024-10-04 16:26:08 -04:00

107 lines
3.3 KiB
Dart

import 'dart:async';
import 'package:example/entities/user_role.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../entities/auth.dart';
part 'auth_controller.g.dart';
/// A mock of an Authenticated User
const _dummyUser = Auth.signedIn(
id: -1,
displayName: 'My Name',
email: 'My Email',
token: 'some-updated-secret-auth-token',
);
/// This controller is an [AsyncNotifier] that holds and handles our authentication state
@riverpod
class AuthController extends _$AuthController {
late SharedPreferences _sharedPreferences;
static const _sharedPrefsKey = 'token';
@override
Future<Auth> build() async {
_sharedPreferences = await SharedPreferences.getInstance();
_persistenceRefreshLogic();
return _loginRecoveryAttempt();
}
/// Tries to perform a login with the saved token on the persistent storage.
/// If _anything_ goes wrong, deletes the internal token and returns a [User.signedOut].
Future<Auth> _loginRecoveryAttempt() {
try {
final savedToken = _sharedPreferences.getString(_sharedPrefsKey);
if (savedToken == null) throw const UnauthorizedException('No auth token found');
return _loginWithToken(savedToken);
} catch (_, __) {
_sharedPreferences.remove(_sharedPrefsKey).ignore();
return Future.value(const Auth.signedOut());
}
}
/// Mock of a request performed on logout (might be common, or not, whatevs).
Future<void> logout() async {
await Future<void>.delayed(networkRoundTripTime);
state = const AsyncData(Auth.signedOut());
}
/// Mock of a successful login attempt, which results come from the network.
Future<void> login(String email, String password) async {
final result = await Future.delayed(
networkRoundTripTime,
() => _dummyUser,
);
state = AsyncData(result);
}
/// Mock of a login request performed with a saved token.
/// If such request fails, this method will throw an [UnauthorizedException].
Future<Auth> _loginWithToken(String token) async {
final logInAttempt = await Future.delayed(
networkRoundTripTime,
() => true, // edit this if you wanna play around
);
if (logInAttempt) return _dummyUser;
throw const UnauthorizedException('401 Unauthorized or something');
}
/// Internal method used to listen authentication state changes.
/// When the auth object is in a loading state, nothing happens.
/// When the auth object is in an error state, we choose to remove the token
/// Otherwise, we expect the current auth value to be reflected in our persistence API
void _persistenceRefreshLogic() {
ref.listenSelf((_, next) {
if (next.isLoading) return;
if (next.hasError) {
_sharedPreferences.remove(_sharedPrefsKey);
return;
}
next.requireValue.map<void>(
signedIn: (signedIn) => _sharedPreferences.setString(_sharedPrefsKey, signedIn.token),
signedOut: (signedOut) {
_sharedPreferences.remove(_sharedPrefsKey);
},
);
});
}
}
/// Simple mock of a 401 exception
class UnauthorizedException implements Exception {
const UnauthorizedException(this.message);
final String message;
}
/// Mock of the duration of a network request
final networkRoundTripTime = 2.seconds;