Check if user is logged in or not before authentication using google sign in - flutter - firebase

In my flutter app, am trying to check if a user is logged in or not before authenticating the user in firebase, so if he is not then do not authenticate
Future<String> loginUserWithGoogle() async {
String returnValue = "error";
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
UserData _user = UserData();
try {
GoogleSignInAccount _googleUser = await _googleSignIn.signIn();
GoogleSignInAuthentication _googleAuth = await _googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(idToken: _googleAuth.idToken, accessToken: _googleAuth.accessToken);
UserCredential _authResult = await _auth.signInWithCredential(credential);
if (_authResult.additionalUserInfo.isNewUser) {
String userGoogleName = _authResult.user.displayName;
List userSplitName = userGoogleName.split(" ");
String userGoogleFirstName = userSplitName.first;
String userGoogleLastName = userSplitName.last;
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('googleUid', _authResult.user.uid);
await prefs.setString('googleEmail', _authResult.user.email);
await prefs.setString('googleFirstName', userGoogleFirstName);
await prefs.setString('googleLastName', userGoogleLastName);
await prefs.setString('googleUserType', "user");
returnValue = "new";
} else {
_currentUser = await RealDatabase().getUserData(_authResult.user.uid);
if (_currentUser != null) {
returnValue = "success";
}
}
} on PlatformException catch (e) {
returnValue = e.message;
} catch (e) {
print(e);
}
return returnValue;
}
}
Here what I want to check is that if it is a new user then save his google data in sharedpreference and take him to another page where he can complete some other registration and then sign him in. but what this code does is that if it is a new user it will authenticate, save the info in sharedpeference and then take him to the page and if maybe that user decided to go back to the previous page (since i use Navigator.push(context)) and still click the google sign in button again then it will take him to the home screen without him completing the other registration I want him to do because it already authenticated him first. So please is there a way to do this without first authenticating the user.

You can use stream provider to control if user logged in or not.
Here is an example how to use stream provider in your project;
https://flutterbyexample.com/lesson/stream-provider.

Related

facebookAuthCredential.idToken is null in Flutter

I successfully integrated facebook login in android and ios, I also getting facebook access token on login but getting null in token id. Below is the code
Future<UserCredential> signInWithFacebook() async {
// Trigger the sign-in flow
final LoginResult loginResult = await FacebookAuth.instance.login();
// Create a credential from the access token
final OAuthCredential facebookAuthCredential =
FacebookAuthProvider.credential(loginResult.accessToken!.token);
print(facebookAuthCredential.idToken); //Here getting null
// Once signed in, return the UserCredential
return FirebaseAuth.instance.signInWithCredential(facebookAuthCredential);
}
You won't get an idToken at this point with Facebook authentication flow, only accessToken. Use the following code skeleton to manage Facebook sign-in and evaluate the results at specific lines with breakpoints:
Future<UserCredential> signInWithFacebook() async {
final LoginResult loginResult = await FacebookAuth.instance.login();
if (loginResult.status == LoginStatus.success) {
final AccessToken accessToken = loginResult.accessToken!;
final OAuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
try {
return await FirebaseAuth.instance.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
// manage Firebase authentication exceptions
} catch (e) {
// manage other exceptions
}
} else {
// login was not successful, for example user cancelled the process
}
}
Then you can call this function with await, and once the future is completed, you can access user data:
final userCredential = await signInWithFacebook();
if (userCredential != null) {
// here you will have your Firebase user in:
// userCredential.user
final idToken = userCredential.user!.getIdToken();
}

Update a Firebase anonymous user using Email & Password

People can sign into my app anonymously:
FirebaseAuth.instance.signInAnonymously();
Once inside, they have a little indicator that reminds them if they want be able to save their data across phones, they'll need to sign in. For Google authentication that looks like this:
Future<void> anonymousGoogleLink() async {
try {
final user = await auth.currentUser();
final credential = await googleCredential();
await user.linkWithCredential(credential);
} catch (error) {
throw _errorToHumanReadable(error.toString());
}
}
where googleCredential is:
Future<AuthCredential> googleCredential() async {
final googleUser = await _googleSignIn.signIn();
if (googleUser == null) throw 'User Canceled Permissions';
final googleAuth = await googleUser.authentication;
final signInMethods = await auth.fetchSignInMethodsForEmail(
email: googleUser.email,
);
if (signInMethods.isNotEmpty && !signInMethods.contains('google.com')) {
throw 'An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address.';
}
return GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
}
Now I'd like to do the same thing but instead link the current anonymous account with email and password authentication.
EDIT:
Found a possible solution while writing this. Leaving for others to possibly see. Feel free to correct me.
On the FirebaseUser object the method updatePassword has this doc string:
/// Updates the password of the user.
///
/// Anonymous users who update both their email and password will no
/// longer be anonymous. They will be able to log in with these credentials.
/// ...
You should just be able to update both and then they'll be authenticated as a regular user. For me that looked like:
Future<void> anonymousEmailLink({
#required FirebaseUser user,
#required String email,
#required String password,
}) async {
try {
await Future.wait([
user.updateEmail(email),
user.updatePassword(password),
]);
} catch (error) {
throw _errorToHumanReadable(error.toString());
}
}

Using user's Google account profile picture in Flutter app

Good day, I am currently using firebase/flutter to create an app where users can log in to proceed to the next page. My log in and firebase are all in working order, but an issue I'm having is being able to access the user's profile picture on the initial log in. The gif attached shows it better than I can explain, but basically when I log in the first time, the user image is not there, but if I exit and go back in (user still signed in) the picture is then loaded:
Issue: User's profile picture does not load on first log in and only appears if exiting and re-entering while still logged in.
Assumption: The user's data does not have enough time to load in by the time my data has finished loading. OR: I am not calling the data correctly on initial log in.
What I'm aiming for: Have the user's details (photo/name/email) loaded on clicking of the login button and before the next page is fully loaded.
Code:
Signin button:
Widget _signInButton() {
return OutlineButton(
splashColor: Colors.grey,
onPressed: () async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
bool result = await signInWithGoogle(); //assumed issue
if (result) {
Navigator.pushNamed(context, '/specials-page');
fireBaseAnalyticsDataObject.onLogin(result);
}
else
print("error logging in");
}
} on SocketException catch (_) {
noInternetAlertDialog(context);
print('not connected');
}
},
sign-in.dart:
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = new GoogleSignIn();
final MyTabsState tabPageObject = new MyTabsState();
Future<bool> signInWithGoogle() async {
try{
final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
final AuthResult authResult = await _auth.signInWithCredential(credential);
final FirebaseUser user = authResult.user;
MyTabs(
userDisplayName: user.displayName,
userPhotoUrl: user.photoUrl,
userEmail: user.email,
);
globalData.user = user; //this accesses .uid / .displayName / .email / .photoUrl
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await _auth.currentUser();
assert(user.uid == currentUser.uid);
return true;
} catch (error) {
return false;
}
}
Thank you for any and all help.
If a there's a variable that on the page that has just been updated, calling it inside setState() might solve the issue. You need to check if Image.network() is being fed with the expected user photo URL after login because the image works as expected on app resume. The code you provided doesn't demonstrate how the photo URL is being accessed and displayed on the landing page.

Firebase/Flutter: reload() not refreshing user.isEmailVerified

I'm sending a verification link when a user registers in the app, but when I try to create a stream that listens for when the user has clicked the verify link in the email.
I'm aware that I somehow need to refresh the user token, but I can't seem to get it to work. I thought reload() method was the one, but maybe I'm just not implementing it correctly.
The problem is that the Stream always returns isEmailVerified == false, only way to make it true is for the user to log out and log in again, which is something I'd like to avoid. How do I do this?
I've created this future:
//CHECKS IF EMAIL IS VERIFIED
Future<bool> checkIfEmailIsVerified() async {
FirebaseUser currUser = await _auth.currentUser();
await currUser.reload();
currUser = await _auth.currentUser();
final bool flag = currUser.isEmailVerified;
if (currUser != null) {
return flag;
} else {
return false;
}
}
and this stream:
//IS EMAILVERIFIED STREAM
Stream<EmailVerified> get emailVerified async* {
final bool isEmailVerified = await checkIfEmailIsVerified();
yield EmailVerified(isEmailVerified);
}
Unfortunately it's necessary to get fresh instance of the user after reload:
User user = FirebaseAuth.instance.currentUser;
if (user != null) {
await user.reload();
user = FirebaseAuth.instance.currentUser;
if (user.emailVerified) {
...
}
}
Try using a single await over chained futures
FirebaseUser currUser = await _auth.currentUser().then((u) => u.reload().then((_) => _auth.currentUser()));
final bool flag = currUser.isEmailVerified;

How to use timer in flutter app for changing data?

I am using shared preferences to store the token, email, username and other user details when a user logs in using firebase authentication. The firebase token expires in every one hour so I need to refresh the token on the basis of when the user has returned to the app which I am doing in getCurrentUser() function below. I want to know that if a user has logged in my app, used it for 5 minutes or so and then close the application, will that timer function would still be listening and call the function after the timeout or not?
If it doesn't do so then How can I achieve checking this?
void checkTokenValidity(int time) {
Timer(Duration(seconds: time), () async {
print('token timed out');
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('token', 'expired');
prefs.remove("currentUser");
});
}
Future<String> getCurrentUser() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String currentToken = prefs.getString('token');
final String cuser = prefs.getString('currentUser');
print("current: $cuser");
if (cuser != null && currentToken != 'expired') {
print('signed in and $currentToken');
SharedPreferences prefs = await SharedPreferences.getInstance();
String token = prefs.getString('token');
String uid = prefs.getString('userId');
String email = prefs.getString('userEmail');
String photo = prefs.getString('photo');
_authenticatedUser =
User(email: email, id: uid, token: token, photo: photo);
return 'success';
} else if (currentToken == 'expired') {
print('token is expired');
final FirebaseUser user = await FirebaseAuth.instance.signInAnonymously();
var token = await user.getIdToken();
prefs.setString('token', token);
String uid = prefs.getString('userId');
String email = prefs.getString('userEmail');
String photo = prefs.getString('photo');
_authenticatedUser =
User(id: uid, email: email, token: token, photo: photo);
checkTokenValidity(3600);
return 'token';
} else {
print('user is null');
return null;
}
}
In my authentication function which is not here, I have called checkTokenValidity(3600) just after the user successfully logs in.
I have also tried using FirebaseUser user = await FirebaseAuth.instance.currentUser(); but that also didn't solve the problem.
You went the wrong way. The right way is to add error handler on 401 (Unauthorized) error and handle it by refreshing token and retrying the same query.

Resources