Using user's Google account profile picture in Flutter app - firebase

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.

Related

Google Sign In current user return null Flutter

I want to integrate my app with Calendar API from Google. And in order to use it, I have to have an AuthClient (which is obtained from _googleSignIn.authenticatedClient();). The problem is, my GoogleSignIn().currentUser always return null and I don't know why. I already use Firebase Auth and Google Sign In.
This is my signInWithGoogle method:
Future signInWithGoogle() async {
try {
await GoogleSignIn().disconnect();
await FirebaseAuth.instance.signOut();
} catch (e) {
print(e.toString());
}
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await GoogleSignIn(scopes: [CalendarApi.calendarScope]).signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth =
await googleUser!.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Once signed in, return the UserCredential
UserCredential result =
await FirebaseAuth.instance.signInWithCredential(credential);
User user = result.user!;
// note: this line always return null and I don't know why
print('current user auth ${GoogleSignIn().currentUser.toString()}');
return _userFromFirebaseUser(user);
}
Did I do something wrong in my code? Any help will be appreciated, thank you!
I also had the issue of GoogleSignIn().currentUser always being null but managed to (finally!) fix it by only initialising GoogleSignIn() once.
For those who want more details: I did this by creating a class called AuthManager that handles everything authentication-related, and making GoogleSignIn one of the parameters required to initialise it (since I'm using Firebase, this was the other parameter):
class AuthManager {
final FirebaseAuth _auth;
final GoogleSignIn _googleSignIn;
AuthManager(this._auth, this._googleSignIn);
Future signInWithGoogle() async {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
// etc....
}
GoogleSignInAccount? get googleAccount {
return _googleSignIn.currentUser;
}
}
And I initiaised by AuthManager class ONCE at the top of my app in a Provider, meaning that I can access it anywhere in my app.
In main.dart:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// To use AuthManager throughout app without initialising it each time
Provider<AuthManager>(
create: (_) => AuthManager(
FirebaseAuth.instance,
GoogleSignIn(scopes:
// Put whatever scopes you need here
),
),
),
// etc...
(Note: I used MultiProvider as I had other things I wanted to put, but if you only have one, you can obviously just go straight to Provider).
Now I can successfully get the current google user by getting googleAccount through my AuthManager class.

Firebase reauthenticate during startup with Flutter

The idea is here to keep the user logged in even after the user has closed the app and relaunch it at much later time.
I have the following code which logs in user and then try to store the tokens
Future<User> loginSocialFirebaseGoogle() async {
GoogleSignInAccount signInResult = await _googleSignIn.signIn();
GoogleSignInAuthentication auth = await signInResult.authentication;
final AuthCredential authCredential = GoogleAuthProvider.getCredential(
idToken: auth.idToken, accessToken: auth.accessToken);
final AuthResult authResult =
await _auth.signInWithCredential(authCredential);
final FirebaseUser firebaseUser = authResult.user;
assert(!firebaseUser.isAnonymous);
assert(await firebaseUser.getIdToken() != null);
final FirebaseUser currentUser = await _auth.currentUser();
assert(firebaseUser.uid == currentUser.uid);
final SharedPreferences pref = await SharedPreferences.getInstance();
pref.setString("idToken", auth.idToken);
pref.setString("accessToken", auth.accessToken);
pref.setString("provider", describeEnum(Provider.Google));
return await authResultToUser(authResult, Provider.Google);
}
Another portion of the code is called during app startup
Future<User> getCurrentUser() async {
final SharedPreferences pref = await SharedPreferences.getInstance();
if (pref != null) {
AuthCredential authCredential;
String provider = pref.getString("provider");
Provider providerEnum;
if (provider != null) {
if (provider == describeEnum(Provider.Google)) {
authCredential = GoogleAuthProvider.getCredential(
idToken: pref.getString("idToken"),
accessToken: pref.getString("accessToken"));
providerEnum = Provider.Google;
} else if (provider == describeEnum(Provider.Facebook)) {
authCredential = FacebookAuthProvider.getCredential(
accessToken: pref.getString("accessToken"));
providerEnum = Provider.Facebook;
}
final AuthResult authResult =
await _auth.signInWithCredential(authCredential);
return await authResultToUser(authResult, providerEnum);
}
}
return null;
}
However I am getting error during _auth.signInWithCredential(authCredential), the error is
`ERROR_INVALID_CREDENTIAL` - If the credential data is malformed or has expired.
Looks like what I stored in the SharedPreferences are wrong. Any idea how to fix this ?
The idea is here to keep the user logged in even after the user has closed the app and relaunch it at much later time.
Firebase already keeps the user signed in, and automatically tries to restore their session without you needing to write any code for it.
But since this may require a call to the server, you may be reading _auth.currentUser before that process has completed. To detect the authentication state properly, use a listener to authStateChanges as shown in the documentation:
FirebaseAuth.instance
.authStateChanges()
.listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});

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

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.

How to navigate on the another page after google sign in

Future<void> signUpWithGoogle() async {
try {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final FirebaseUser user =
(await _auth.signInWithCredential(credential)).user;
return user;
} catch (error) {
print(error);
}
}
I have done that above code to sign in with google and called the function of google sign button like this
signUpWithGoogle().then((value) => Navigator.of(context).push(MaterialPageRoute(builder: (_) {
return HomePage();
})));
But on first time when apk is installed normally the app is asking for choosing the google account
But after log out when i tap on the google SignIn button it is not asking in pop up menu to select the account.
And one more problem is their on clicking on the google signin button firsts it goes to the HomePage() then signIn is hapenning.
#override
void initState() {
super.initState();
getCurrentUser();
}
Future<void> getCurrentUser() async {
FirebaseUser user = await _auth.currentUser();
bool result = await facebookSignIn.isLoggedIn;
if (user != null && user.isEmailVerified == true) {
print("Email");
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) {
return HomePage();
}));
}
}
Is this is the correct method to navigate to the HomePage() for those users who is signed in when app starts
Whenever user logout and sign in again with google account android is smart enough to provide google account directly to the app without giving sign in pop up
for your second problem plz ref this answer -:
Why might a function not get called in initState(){ super.initState()} in flutter, but work perfectly fine if called later in the same page?
first of all i am also new to flutter so this might not be the best solution however this is what i implement in my app for the authentication part
first i created a User class that contain an ID for the user
then i created a stream of user to my app so the app will always be provided with this value and what ever change happen to it in my services this is the code
final FirebaseAuth _auth = FirebaseAuth.instance;
Stream<FirebaseUser> user; // firebase user
User _userFromFireBaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
// //auth change user stream
Stream<User> get userStream {
return _auth.onAuthStateChanged.map(_userFromFireBaseUser);
// or we can use .map((FirebaseUser user) => _userFromFireBaseUser(user) );
}
in my main widget
StreamProvider<User>.value(
lazy: false,
value: AuthService().userStream,
child: MaterialApp()//your main widget
then i created a statless wrapper class that read the stream value and according move to a page, in my app i used the wrapper to go to the sign in if the user is null else go to the profile page, in your application i guess it will go to the homepage
class ProfileWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
//print(user.uid);
if (user == null) {
print('no user');
return SignUpPage();
} else if (user != null) {
print('there is user');
print(user.uid);
return ProfilePage();
// print('there is user' + user.displayname);
// print('there is user' + user.photourl);
}
}
}
also you need to add the provider package in your pubspec.yaml file
provider: ^4.1.1
by doing this you don't need to handle any navigation between the home and the sign up, if your user is signed in you will automatically be navigated to the home page.
another solution if that is not what you are looking for, after the google sign function finishes check if the firebase user is not null, if there is a user navigate to your homepage

Firebase Auth not working in Flutter

Somehow Firebase Authentication doesn't work in my Implementation. I implemented Google SignIn with Firebase Authentication following the Firebase Codelab, but changed the ensureLoggedIn function to:
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = googleSignIn.currentUser;
print('SIGNIN');
if (user == null) user = await googleSignIn.signInSilently();
if (user == null) {
user = await googleSignIn.signIn();
print('LOGIN');
}
if (auth.currentUser == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
print('CURRENTUSER');
}
}
where I used the console outputs to verify which parts of the function are executed. The function call happens every time the app is started, by calling it in the main() function as
main() async{
new LinearProgressIndicator(backgroundColor: Colors.lightGreen,);
await _ensureLoggedIn();
runApp(new SpringsterApp());
}
I noticed however that although the Signin prompt with the available Google Accounts on the device happens every time the user has not signed in yet, the last part of the ensureLoggedIn function (if (auth.currentUser == null)) never gets executed, regardless of whether or not the user is signed in.
I noticed this since the print('CURRENTUSER'); is never put out in the console, and additionaly because no new user is created in the Firebase Auth console. Does anyone know why this is happening and how I could maybe fix this?
My Firestore security rules are set to auth only, so no data can be written or read as long as this error occurs. Thanks in advance!
To anyone interested, I was able to sign in as planned with:
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = googleSignIn.currentUser;
if (user == null) user = await googleSignIn.signInSilently();
if (user == null) {
final GoogleSignInAccount googleUser = await googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final FirebaseUser user = await auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await auth.currentUser();
assert(user.uid == currentUser.uid);
}
}
i already initilized firebase app. But still not connect to firebase auth. So i follow from pub dev docs firebase_auth.
ran on emulator.
bool shouldUseFirebaseEmulator = false; // before main()
if (shouldUseFirebaseEmulator) {
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
} // inside main()

Resources