Firebase Auth authStateChanges trigger - firebase

I'm using firebase to authenticate a user and create a user in firestore database :
final auth.FirebaseAuth _firebaseAuth;
Future<void> signUp(
{#required String email, #required String password}) async {
assert(email != null && password != null);
try {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
await createUserInDatabaseIfNew();
} on Exception {
throw SignUpFailure();
}
}
With firebase, once the method .createUserWithEmailAndPassword() is executed, it triggers right after the authStateChanges which I am using in my code to send the new user in the user stream, and eventually retrieve its data from the database
Stream<User> get user {
return _firebaseAuth.authStateChanges().map((firebaseUser) {
return firebaseUser == null ? User.empty : firebaseUser.toUser;
});
}
StreamSubscription<User> _userSubscription = _authenticationRepository.user.listen((user) {
return add(AuthenticationUserChanged(user));}
if(event is AuthenticationUserChanged){
if(event.user != User.empty){
yield AuthenticationState.fetchingUser();
User userFromDatabase;
try{
var documentSnapshot = await _firebaseUserRepository.getUser(event.user.id);
userFromDatabase = User.fromEntity(UserEntity.fromSnapshot(documentSnapshot));
yield AuthenticationState.authenticated(userFromDatabase);
}
The problem that I am facing, is that because of _firebaseAuth.createUserWithEmailAndPassword, _firebaseAuth.authStateChanges is triggerd before the user is created in the database, and eventually when I try to retrieve that user, it still does not exist in the database.
I would like _firebaseAuth.authStateChanges() to be triggered after my method createUserInDatabaseIfNew runs.
How could I achieve that ?

I would like _firebaseAuth.authStateChanges() to be triggered after my method createUserInDatabaseIfNew runs.
The auth state change listener fires when the user's authentication state change, which is when their sign in completes. There's no way to change this behavior, nor should there be.
If you want to trigger when the user's registration in your app has completed, you should respond to events that signal that. So if registration means that the user is written to the database, you can use a onSnapshot listener on the database to detect user registration.
You could even combine the two:
Use an auth state change listener to detect when the sign in completes.
Inside that auth state listener, then attach a snapshot listener for the user's registration document.

Related

Flutter sign in with google lost authentication when the internet connection lost, how to re-auth it?

We are currently building an app that requires only google sign in and communicate with the Firebase depending on those data that we get with sign in.
The problem is this; after logging in to the app if the user lost internet connection and then re-establishes that connection without closing the app, FirebaseAuth.currentUser is not null but at the same time the user is not authenticated so the functions that i need to communicate with the DB is not working.
My question is simply this;
How do i check for the authentication and then re-authanticate the user.
My signin method is below, i tried to re-sign in every time when user enters some certain pages but that just not seemed good but does solves the problem. Is there a optimized way?
Future<String> signInWithGoogle() async {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await _googleSignIn!.signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser!.authentication;
// Create a new credential
final GoogleAuthCredential credential = (GoogleAuthProvider.credential(
accessToken: googleAuth!.accessToken,
idToken: googleAuth.idToken,
) as GoogleAuthCredential);
// Once signed in, return the UserCredential
final UserCredential? authResult =
await _auth!.signInWithCredential(credential);
final User? user = authResult!.user;
//assert(user!.isAnonymous);
assert(await user!.getIdToken() != null);
final User? currentUser = _auth!.currentUser;
assert(user!.uid == currentUser!.uid);
return 'signInWithGoogle succeeded: $user';
}
Edit:
We were using connectivity_plus plugin so we created a listener for it and if the user changes its connection to wifi or mobile we simply re-signin them. It worked for us it may have work for you as well;
#override
void initState() {
subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
if (result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi ||
result == ConnectivityResult.ethernet) {
Login().signInWithGoogle();
}
print("${result}");
});
// TODO: implement initState
super.initState();
}
dispose() {
super.dispose();
subscription.cancel();
}
The Flutter Firebase docs have a re-authenticate user option and it sounds quite similar to your situation.
Re-authenticate a user

How to auto signOut a firebase user in flutter?

I'm working on a flutter app with firebase as a backend and an authentication, now I want the app to have a trial version where the user can signInWithGoogle and use the app with full features ( for like one hour ), and then signs out automatically after that one hour ends.
SO FAR, I accomplished the following:1- signIn with google account.
2- add signed in user to _trial collection with timeStamp.
Future enterTrial() async {
final UserCredential user = await Auth.instance.googleSignInMethod();
final User actualUser = user.user;
try {
//_trial is a collection for users in Trial Period
await _trial.doc(actualUser.email).set({.
"name": actualUser.displayName,
"email": actualUser.email,
"creationDate": FieldValue.serverTimestamp(),
});
} catch (e) {
print(e);
}
}
WHAT's LEFT:
after one hour from signIn, signOut and move user from _trial collection to _expired collection.
so that I could check if the user is expired or not.
is there a way to automatically signOut after sometime? or to - periodically - compare creationTime with currentTime and accordingly signOut?
Thanks In Advance.
Yes You can achive this by using background_fetch package this provide you a callback function in that function you can call your logout method.

How to check the particular field in the firestore after authentication in flutter?

I have a field in firestore "isEnabled" it contains boolean value. So if it is equal to true then the user login to the app he prompted to the home page.
If the field "isEnabled" is equal to false then a message should be displayed your account is disabled.
For now I am able to do login and after that the field is checked and if it is false the user is logged out.
Is there any way in which first authentication takes place, then the user is taken to a page where it shows "Checking few more details.." kind of a loading page and if the "isEnabled" field is false it will show your account has been disabled.
I tried doing the checking of the field before but it's not possible and after
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
As soon as the above line is executed the user is obviously prompted to the home page.
What I am able to achieve that code is shown below -
Future signInWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
print(user.uid);
final firestore = Firestore.instance;
/*
retrieving the fields from the database for checking the isEnabled field
*/
await firestore.collection("admins").document(user.uid).get().then((resp) {
logoutToast();
if ((resp.data["isEnabled"]) == false) {
disableToast();
signOut();
} else {
loginToast();
}
return user;
});
//print(qn);
//return user;
} catch (error) {
print(error.toString());
return null;
}
}
Thanks !!
You can always disable a user in Firebase Auth and he won't be able to login at all.
If you still want to go your way and check a isEnabled on Firebase Firestore, than make the login and DON'T direct the user to the Home Screen, instead direct him to a Verification Screen and if isEnabled is true he goes to the Home Screen, if it's false you log him out.
Another solution is to have a Dialog shown in the Home Screen after the first time it's displayed and that Dialog will run the verification logic.

onAuthStateChanged doesn't get called when email is verified in flutter

When a user signs up in my app he gets a verification e-mail. The onAuthStateChanged-listener gets called when a user gets created using createUserWithEmailAndPassword but not after the email got verified.
I have a separate class which handles all authentications. The following method is to sign up user
Future<FirebaseUser> signUpUser(email, password) async {
final FirebaseUser user = await _auth.createUserWithEmailAndPassword(email: email, password: password);
assert (user != null);
assert (await user.getIdToken() != null);
return user;
}
This method is called in my StatefulWidget using this method
void _signUpUser() async {
try {
await Auth().signUpUser(_email, _password)
..sendEmailVerification();
} catch (e) {
print(e.toString());
}
}
And onAuthStateChanged is set up in the initState method of my StatefulWidget
FirebaseAuth.instance.onAuthStateChanged.listen((user) {
print("Auth State Changed!");
if (user.isEmailVerified) {
print("EMail verified!");
}
}
onAuthStatechanged is triggered only in case of user Login or Logout & not on Email verification.
As Per Doc -
onAuthStatechanged Adds an observer for changes to the user's sign-in state.
The observer will be triggered in the following scenarios:
When auth().onAuthStateChanged() is first called. It will trigger with
the initial Auth state. If the user is returning from an
auth().signInWithRedirect() operation, the observer will wait for that
operation to resolve before initially triggering.
When a new user signs.
When an already signed in user signs out.
At the moment I'm providing a button to 'complete email validation'. This calls User.reload - so this appears to be enough per #Frank van Puffelen's comment above. It's much less satisfactory than getting a status update event; I may implement a loop to check the status for a period after sending an email so that the app passes through automatically.
You can simply do a firebase.auth().signOut after sending the email verification link. And when the user clicks on sign in again, it'll automatically fire onAuthStateChanged.
Use the where Function:
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
Stream<FirebaseUser> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.where((user)=> user.isEmailVerified);
}

FirebaseAuth current user is not returning null after calling sign out?

I am trying to logout user and switch the widgets but after calling the following function -
void logoutUser() async {
await FirebaseAuth.instance.signOut();
}
If I check for the current user, it's returning user object but with null id -
FirebaseAuth.instance.currentUser()
I try to kick the user out to main.dart after logout which checks if user is signed in or not and loads a correct widget. Does anyone have any idea why currentUser() isn't returning null after calling signOut()?
_auth.currentUser() is probably returning an anonymous FirebaseUser object. Check the isAnonymous property.
Example :
auth.currentUser().then((user) {
if (user == null || user.isAnonymous) {
// what will you do?
return;
}
setState(() {
_uid = user.uid;
});
});
Yet, I would highly recommend to monitor the onAuthStateChanged stream instead. This way you will be informed when the user logs in or logs out immediately.
Check this article, it covers it in depth.

Resources