I'm facing an issue with state management in my Flutter app, specifically with updating the UI after logging in. While my AuthProvider
works well for logging out and updates the UI accordingly, it fails to do the same for logging in. Despite successfully logging in, the UI doesn't reflect the authenticated state until I restart the app.
Here's the relevant part of my code:
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => AuthProvider(),
child: MaterialApp(
initialRoute: '/map_selection',
onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings),
),
);
}
To navigate to the login screen, I'm pushing it onto the navigator stack and popping it once the authentication is successful, like so:
if (_formKey.currentState!.validate()) {
dynamic result = await _authProvider.signInWithEmail(emailController.text, passwordController.text);
if (result is String) {
setState(() => error = result);
} else {
if (context.mounted) {
Navigator.pop(context);
}
}
}
To sign into Firebase
I use the signInWithEmail method in AuthProvider
:
Future signInWithEmail(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(email: email.trim(), password: password.trim());
User? user = result.user;
notifyListeners();
return user;
} catch (e) {
List<String> error = e.toString().split(']');
return error[1].trim();
}
}
After logging in, the expected behavior is for the UI to update and show a logout button instead of the login button, without needing to restart the app. When debugging, I notice that the app does not notify listeners after signing in but instead jumps back to the login screen's Navigator.pop
, leading to the home screen without updating.
Does anyone have insights on why the UI isn't updating after login and how I might resolve this issue?
Thank you in advance!
I have solved my issue using Riverpod
. Without boring you with the details, here the relevant parts:
I have a Riverpod provider to which the relevant pages listen via ref.watch(authNotifierProvider)
. Whenever an AuthChange
happens, the state of the provider is updated and all the listeners automatically rebuild.
The AuthState class looks like this:
class AuthState {
final MyUser? user;
final bool isLoading;
final String? error;
AuthState({
this.user,
this.isLoading = false,
this.error,
});
AuthState copyWith({
MyUser? user,
bool? isLoading,
String? error,
}) {
return AuthState(
user: user ?? this.user,
error: error ?? this.error,
);
}
}
The AuthNotifier:
class AuthNotifier extends StateNotifier<AuthState> {
final Ref ref;
final FirebaseAuth _auth = FirebaseAuth.instance;
AuthNotifier(this.ref) : super(AuthState()) {
_auth.authStateChanges().listen(_onAuthStateChanged);
}
Future<void> _onAuthStateChanged(User? firebaseUser) async {
if (firebaseUser == null) {
state = state.copyWith(user: null);
} else {
MyUser myUser = await _createMyUserFromFirebaseUser(firebaseUser);
ref.read(myUserProvider.notifier).state = myUser;
state = state.copyWith(user: myUser);
}
}
Future<MyUser> _createMyUserFromFirebaseUser(User firebaseUser) async {
bool isAdmin = firebaseUser.uid == "xyz";
MyUser myUser = MyUser(
uid: firebaseUser.uid,
email: firebaseUser.email!,
isAdmin: isAdmin,
isEmailVerified: firebaseUser.emailVerified,
favorites: []
);
ref.read(myUserProvider.notifier).state = myUser;
await ref.read(myUserProvider.notifier).loadFavorites();
return myUser;
}
Future<void> signInWithEmail(String email, String password) async {
state = state.copyWith(isLoading: true);
try {
UserCredential result = await _auth.signInWithEmailAndPassword(email: email.trim(), password: password.trim());
state = state.copyWith(user: await _createMyUserFromFirebaseUser(result.user!), isLoading: false);
} catch (e) {
state = state.copyWith(isLoading: false, error: e.toString());
}
}
The Provider:
final authNotifierProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(ref);
});
Whenever any widget has ref.watch(authNotifierProvider)
in it, it is rebuild whenever the state of the provider changes.