I'm trying to implement declarative navigation in my Flutter app using the AutoRoute
package. I have a complex navigation structure defined in my AppRouter
class, and I'm using the AuthCubit
for managing user authentication status. I would like to achieve declarative navigation based on the user's authentication status using AutoRoute
.
auth_cubit.dart:
part 'auth_state.dart';
part 'auth_cubit.freezed.dart';
@injectable
class AuthCubit extends Cubit<AuthState> {
final AuthStateRepository _authStateRepository;
StreamSubscription<bool>? _authSubscription;
AuthCubit(this._authStateRepository) : super(const AuthState.isLoggedIn()) {
_init();
}
void _init() {
_authSubscription =
_authStateRepository.isUserLoggedIn.listen((isLoggedIn) {
if (isLoggedIn) {
emit(const AuthState.isLoggedIn());
} else {
emit(const AuthState.isLoggedOut());
}
});
}
@override
Future<void> close() {
_authSubscription?.cancel();
return super.close();
}
}
auth_state.dart:
part of 'auth_cubit.dart';
@freezed
class AuthState with _$AuthState {
const factory AuthState.isLoggedIn() = _IsLoggedIn;
const factory AuthState.isLoggedOut() = _IsLoggedOut;
}
auth_state_repository.dart:
@lazySingleton
class AuthStateRepository {
final BehaviorSubject<bool> _isUserLoggedIn = BehaviorSubject<bool>();
void setUserLoggedIn(bool isLoggedIn) {
_isUserLoggedIn.add(isLoggedIn);
}
Stream<bool> get isUserLoggedIn => _isUserLoggedIn.stream;
}
token_repository.dart:
@singleton
class TokenRepository {
TokenRepository(this._authStateRepository) {
_init();
}
void _init() {
_checkForAccessToken();
_checkForRefreshToken();
}
final AuthStateRepository _authStateRepository;
String? _accessToken;
String? _refreshToken;
//get refresh token
FutureOr<String?> get refreshToken async {
if (_refreshToken != null) {
return _refreshToken;
}
return readRefreshToken().then((token) {
_refreshToken = token;
return token;
});
}
//get access token
FutureOr<String?> get accessToken async {
if (_accessToken != null) {
return _accessToken;
}
return readAccessToken().then((token) {
_accessToken = token;
return token;
});
}
//check if token is expired
Future<bool> tokenIsExpired() async {
final token = await readAccessToken();
return token == null || token.isEmpty;
}
//load token from secure storage
Future<void> loadAccessToken() async {
final token = await readAccessToken();
if (token != null) {
_accessToken = token;
}
}
//load refresh token from secure storage
Future<void> loadRefreshToken() async {
final token = await readRefreshToken();
if (token != null) {
_refreshToken = token;
}
}
//check if refresh token exists
Future<bool> _hasRefreshToken() async {
final token = await readRefreshToken();
return token != null && token.isNotEmpty;
}
Future<void> _checkForRefreshToken() async {
_hasRefreshToken().then((hasToken) {
_authStateRepository.setUserLoggedIn(hasToken);
});
}
//check if access token exists
Future<bool> _hasAccessToken() async {
final token = await readAccessToken();
return token != null && token.isNotEmpty;
}
Future<void> _checkForAccessToken() async {
_hasAccessToken().then((hasToken) {
_authStateRepository.setUserLoggedIn(hasToken);
});
}
//read refresh token from secure storage
Future<String?> readRefreshToken() async {
try {
const secureStorage = FlutterSecureStorage();
return secureStorage.read(key: _refreshTokenKey);
} catch (e) {
return null;
}
}
//read access token from secure storage
Future<String?> readAccessToken() async {
try {
const secureStorage = FlutterSecureStorage();
return secureStorage.read(key: _accessTokenKey);
} catch (e) {
return null;
}
}
//save refresh token to secure storage
Future<bool> saveRefreshToken(String? token) async {
try {
const secureStorage = FlutterSecureStorage();
await secureStorage.write(
key: _refreshTokenKey,
value: token,
);
_refreshToken = token;
return true;
} catch (e) {
return false;
}
}
//save access token to secure storage
Future<bool> saveAccessToken(String? token) async {
try {
const secureStorage = FlutterSecureStorage();
await secureStorage.write(
key: _accessTokenKey,
value: token,
);
_accessToken = token;
return true;
} catch (e) {
return false;
}
}
Future<void> refreshAccessToken() async {
if (await tokenIsExpired()) {
final refreshToken = await readRefreshToken();
final accessToken = await readAccessToken();
if (refreshToken != null && accessToken != null) {
final response = await getIt<ApiDatasource>().refreshToken(
UseTokenModel(accessToken: accessToken, refreshToken: refreshToken),
);
final newAccessToken = response.accessToken;
await saveAccessToken(newAccessToken);
_accessToken = newAccessToken;
}
}
}
}
token_interceptor.dart:
@injectable
class TokenInterceptor extends Interceptor {
final TokenRepository _tokenRepository;
TokenInterceptor(this._tokenRepository);
@override
Future<void> onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
final String? token = await _tokenRepository.accessToken;
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
Future<void> onError(
DioException err,
ErrorInterceptorHandler handler,
) async {
if (err.response?.statusCode == 401 || err.response?.statusCode == 403) {
_tokenRepository.refreshAccessToken();
}
handler.next(err);
}
}
I want to conditionally navigate users based on their authentication status. For example, if a user is logged in, they should be directed to the DashboardRoute
page. If they are not logged in, they should be directed to the LoginRoute page
.
How can I achieve declarative navigation in this setup? Should I modify my AppRouter
or my AuthCubit
? Can you provide an example of how to conditionally navigate users using the AutoRoute
package?
Thank you in advance for your help!
Feel free to ask for more details in comments section