I am doing a project with riverpod with annotations after I did the same project without annotations. I have tried to adapt all the providers that I have in the first project, but I am having a problem when accessing the _authNotifier.addListener since it tells me that it is not defined.
I'm not sure what is going on, so it will be helpful y you can help me!
This is my first project's code and it works perfect
**auth_provider.dart**
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
final authRepository = AuthRepositoryImpl();
final keyValueStorageService = KeyValueStorageServiceImpl();
return AuthNotifier(
authRepository: authRepository,
keyValueStorageService: keyValueStorageService,
);
});
class AuthNotifier extends StateNotifier<AuthState> {
final AuthRepository authRepository;
final KeyValueStorageService keyValueStorageService;
AuthNotifier({
required this.authRepository,
required this.keyValueStorageService,
}) : super(AuthState()) {
checkAuthStatus();
}
Future<void> loginUser(String email, String password) async {
try {
final user = await authRepository.login(email, password);
_setLoggedUser(user);
} on CustomError catch (e) {
logout(e.message);
} catch (e) {
logout('Error no controlado');
}
}
void registerUser(String email, String password, String fullName) async {}
void checkAuthStatus() async {
final token = await keyValueStorageService.getKeyValue<String>('token');
if (token == null) return logout();
try {
final user = await authRepository.checkAuthStatus(token);
_setLoggedUser(user);
} catch (e) {
logout();
}
}
_setLoggedUser(User user) async {
await keyValueStorageService.setKeyValue('token', user.token);
state = state.copyWith(
user: user,
authStatus: AuthStatus.authenticated,
errorMessage: '',
);
}
Future<void> logout([String? errorMessage]) async {
await keyValueStorageService.removeKey('token');
state = state.copyWith(
authStatus: AuthStatus.unauthenticated,
user: null,
errorMessage: errorMessage,
);
}
}
enum AuthStatus { checking, authenticated, unauthenticated }
class AuthState {
final AuthStatus authStatus;
final User? user;
final String errorMessage;
AuthState({
this.authStatus = AuthStatus.checking,
this.user,
this.errorMessage = '',
});
AuthState copyWith({
AuthStatus? authStatus,
User? user,
String? errorMessage,
}) =>
AuthState(
authStatus: authStatus ?? this.authStatus,
user: user ?? this.user,
errorMessage: errorMessage ?? this.errorMessage,
);
}
**app_router_notifier.dart**
final goRouterNotifierProvider = Provider((ref) {
final authNotifier = ref.read(authProvider.notifier);
return GoRouterNotifier(authNotifier);
});
class GoRouterNotifier extends ChangeNotifier {
final AuthNotifier _authNotifier;
AuthStatus _authStatus = AuthStatus.checking;
GoRouterNotifier(this._authNotifier) {
_authNotifier.addListener((state) {
authStatus = state.authStatus;
});
}
AuthStatus get authStatus => _authStatus;
set authStatus(AuthStatus value) {
_authStatus = value;
notifyListeners();
}
}
Now this is my second project using annotations (Here is the problem)
**auth_provider.dart**
part 'auth_provider.g.dart';
@Riverpod(keepAlive: true)
class Auth extends _$Auth {
final authRepository = AuthRepositoryImpl();
final keyValueStorageService = KeyValueStorageServiceImpl();
@override
AuthState build() {
checkAuthStatus();
return AuthState();
}
Future<void> loginUser(String email, String password) async {
try {
final user = await authRepository.login(email, password);
_setLoggedUser(user);
} on CustomError catch (e) {
logout(e.message);
} catch (e) {
logout('Error no controlado');
}
}
void registerUser(String email, String password, String username) async {}
void checkAuthStatus() async {
final token = await keyValueStorageService.getKeyValue<String>('token');
if (token == null) return logout();
try {
final user = await authRepository.checkAuthStatus(token);
_setLoggedUser(user);
} catch (e) {
logout();
}
}
_setLoggedUser(User user) async {
await keyValueStorageService.setKeyValue('token', user.token);
state = state.copyWith(
user: user,
authStatus: AuthStatus.authenticated,
errorMessage: '',
);
}
Future<void> logout([String? errorMessage]) async {
await keyValueStorageService.removeKey('token');
state = state.copyWith(
authStatus: AuthStatus.unauthenticated,
user: null,
errorMessage: errorMessage,
);
}
}
enum AuthStatus { checking, authenticated, unauthenticated }
class AuthState {
final AuthStatus authStatus;
final User? user;
final String errorMessage;
AuthState({
this.authStatus = AuthStatus.checking,
this.user,
this.errorMessage = '',
});
AuthState copyWith({
AuthStatus? authStatus,
User? user,
String? errorMessage,
}) =>
AuthState(
authStatus: authStatus ?? this.authStatus,
user: user ?? this.user,
errorMessage: errorMessage ?? this.errorMessage,
);
}
**app_router_notifier.dart**
part 'app_router_notifier.g.dart';
@Riverpod(keepAlive: true)
GoRouterNotifier goRouterNotifier(Ref ref) {
final authNotifier = ref.read(authProvider.notifier);
return GoRouterNotifier(authNotifier);
}
class GoRouterNotifier extends ChangeNotifier {
final Auth _authNotifier;
AuthStatus _authStatus = AuthStatus.checking;
GoRouterNotifier(this._authNotifier) {
_authNotifier.addListener((state) {
authStatus = state.authStatus;
});
}
AuthStatus get authStatus => _authStatus;
set authStatus(AuthStatus value) {
_authStatus = value;
notifyListeners();
}
}
So what is the problem?
As you can see I'm trying to do the exact same thing, but in app_router_notifier.dart
in this line:
_authNotifier.addListener((state) {
authStatus = state.authStatus;
});
I'm getting this error: The method 'addListener' isn't defined for the type 'Auth'. Try correcting the name to the name of an existing method, or defining a method named 'addListener'.
I've tried different solutions even with AI and nothing. I think that there's something wrong in my auth_provider.dart but I'm not sure what it is. I believe that the .addListener
is in the StateNotifier
but it isn't in the NotifierProvider that generates Riverpod.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authHash() => r'baf96bdb5085978702d0e2b04c3a2e56893b8a41';
/// See also [Auth].
@ProviderFor(Auth)
final authProvider = NotifierProvider<Auth, AuthState>.internal(
Auth.new,
name: r'authProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$authHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$Auth = Notifier<AuthState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter
There are other "solutions" that didn't work for me:
_authNotifier.listen<AuthStatus>((state) {
authStatus = state.authStatus;
});
_authNotifier.ref.listen(authProvider.notifier, (_, currentState) {
authStatus = currentState.state.authStatus;
});
_authNotifier.addListener((state) {
final authState = _authNotifier.build();
authStatus = authState.authStatus;
});
A little bit of context of the application
I'm using Flutter, Dart, Riverpod and Go Router to do a login and signup in my backend (NestJS + GraphQL), in the following file I'm trying to redirect the user if doesn't have a valid token. Here is where I'm using the goRouterNotifier so it can listen when the state changes and redirect the user.
part 'app_router.g.dart';
@Riverpod(keepAlive: false)
GoRouter goRouter(GoRouterRef ref) {
final goRouterNotifier = ref.read(goRouterNotifierProvider);
return GoRouter(
initialLocation: '/login',
refreshListenable: goRouterNotifier,
routes: [
//* First Route
GoRoute(
path: '/checking',
builder: (context, state) => const CheckAuthStatusScreen(),
),
//* Shared Routes
GoRoute(
path: '/main/:page',
builder: (context, state) {
final pageIndex = state.pathParameters['page'] ?? '0';
return MainScreen(pageIndex: int.parse(pageIndex));
}),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingdScreen(),
),
//* Auth Routes
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/signup',
builder: (context, state) => const SignUpScreen(),
),
//* Home Routes
],
redirect: (context, state) {
// print(state.matchedLocation);
final isGoingTo = state.matchedLocation;
final authStatus = goRouterNotifier.authStatus;
if (isGoingTo == '/checking' && authStatus == AuthStatus.checking) {
return null;
}
if (authStatus == AuthStatus.unauthenticated) {
if (isGoingTo == '/login' || isGoingTo == '/signup') return null;
return '/login';
}
if (authStatus == AuthStatus.authenticated) {
if (isGoingTo == '/login' ||
isGoingTo == '/signup' ||
isGoingTo == '/checking') return '/main/0';
}
return null;
},
);
}
In fact,
StateNotifier
properties such as.stream
or.addListener
are not present inNotifier
. So you should listen toNotifier
like this:or use the
ref.listen
method (I think this option is the most optimal in this situation):You can pass
ref
to it through the constructor of theGoRouterNotifier
class.In any other cases where you don't have access to
ref
, get the current container from the context (ProviderScope.containerOf(context)
) and useproviderContainer.listen(provider)
.