FlutterFire authentication using role authentication - firebase

I'm trying to sign in based on the role of the user from firebase. But mostly, the tutorials i saw are using older firebase version, therefore I'm using firebase documentation and my logic.
here is my function for auth role sign in
Future<User?> signIn(String email, String password) async {
try {
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email, password: password);
User? user = userCredential.user;
await FirebaseFirestore.instance
.collection('users')
.doc(user!.uid)
.get()
.then((DocumentSnapshot documentSnapshot) {
var docData = documentSnapshot.data();
var role = docData['role'];
if (documentSnapshot.data()['roles'] == 'dosen') {
return 'ok';
} else {
return 'err';
}
});
return user;
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
}
}
but mostly having problem with this section
the error is like this
"The method '[]' can't be unconditionally invoked because the receiver can be 'null'. Try making the call conditional (using '?.') or adding a null check to the target ('!')."
await FirebaseFirestore.instance
.collection('users')
.doc(user!.uid)
.get()
.then((DocumentSnapshot documentSnapshot) {
var docData = documentSnapshot.data();
var role = docData['role'];
if (documentSnapshot.data()['roles'] == 'dosen') {
return 'ok';
} else {
return 'err';
}
});
Thank you.
---------------EDIT---------------
i've solved it using this
Map<String, dynamic> data =
documentSnapshot.data()! as Map<String, dynamic>;
so then i can pass the data using string, in which i call it like this
role = data['role'].toString();
thank you for the help :)

REPLACE THIS:
if (documentSnapshot.data()['roles'] == 'dosen')
WITH:
if (documentSnapshot.data()!['roles'] == 'dosen')

Related

Flutter: have different authentication providers in Firebase

I have different methods in my app to log in:
Facebook
Google
Apple
Email
For the question I'll focus on the first 2 ones. When the user logs in with Facebook the providers look like this:
That's fine but if I log out and log in again, this time with a new Google account but using same email, the providers look like this:
Now, if I log out and log in again with Facebook I face the account-exists-with-different-credential error. Something for which I have the logic prepared and show its provider login method, but this user should have both provider available and he should be able to log in with both methods.
This is my code:
Future facebookSignIn(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if (result.status == LoginStatus.success) {
final AccessToken accessToken = result.accessToken!;
AuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
await _firebaseCredential(context, credential);
}
}
Future googleSignIn(BuildContext context,
[String? email, facebookCredential]) async {
try {
GoogleSignInAccount googleUser;
dynamic popup = await _googleSignIn.signIn();
// cancelled login
if (popup == null) {
return null;
}
googleUser = popup;
GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await _firebaseCredential(context, credential);
} on FirebaseAuthException catch (e) {
// await FirebaseCrashlytics.instance.recordError(
// e,
// StackTrace.fromString("/googleSignIn"),
// reason: e.message,
// );
// return null;
}
}
_firebaseCredential(BuildContext context, credential) async {
try {
User user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user!;
// Provider.of<MyRents>(context, listen: false).updateUI();
await firebaseProfile.updateUserData(context, user);
} on FirebaseAuthException catch (error) {
// final error = e as FirebaseAuthException;
if (error.code == 'account-exists-with-different-credential') {
String email = error.email!;
// AuthCredential pendingCredential = e.credential;
List<String> signInMethods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (signInMethods.first == 'google.com' ||
signInMethods.first == 'facebook.com') {
// TODO: fix facebook
return await googleSignIn(context, email, credential);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
}
}
Am I missing something?
flutter_facebook_auth: ^4.3.3
google_sign_in: ^5.2.1
Future googleSignIn(BuildContext context,
[String? email, facebookCredential]) async {
try {
GoogleSignInAccount googleUser;
dynamic popup = await _googleSignIn.signIn();
// cancelled login
if (popup == null) {
return null;
}
googleUser = popup;
GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await _firebaseCredential(context, credential);
} on FirebaseAuthException catch (e) {
// await FirebaseCrashlytics.instance.recordError(
// e,
// StackTrace.fromString("/googleSignIn"),
// reason: e.message,
// );
// return null;
}
}
Future facebookSignIn(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if (result.status == LoginStatus.success) {
final AccessToken accessToken = result.accessToken!;
AuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
await _firebaseCredential(context, credential);
}
}
// other methods...
_firebaseCredential(BuildContext context, credential) async {
try {
User user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user!;
await firebaseProfile.updateUserData(context, user);
} on FirebaseAuthException catch (error) {
if (error.code == 'account-exists-with-different-credential') {
String email = error.email!;
List<String> signInMethods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
// bool newUser = (signInMethods.length > 0) ? false : true;
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
var user;
switch (signInMethods.first) {
case 'google.com':
user = await googleSignIn(context, email, credential);
break;
case 'facebook.com':
user = await facebookSignIn(context);
break;
case 'apple.com':
user = await appleSignIn(context);
break;
case 'password':
// since password is managed by user we force have email provider only
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.signInMethods_password'))));
break;
// TODO: apple
}
await linkProvider(context, credential);
return user;
}
return ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
}
// just some extra error covering
Future linkProvider(BuildContext context, credential) async {
try {
await FirebaseAuth.instance.currentUser?.linkWithCredential(credential);
} on FirebaseAuthException catch (e) {
switch (e.code) {
case "provider-already-linked":
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.provider_already_linked'))));
break;
case "invalid-credential":
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(translate('auth.invalid_credential'))));
break;
case "credential-already-in-use":
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.credential_already_in_use'))));
break;
default:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(translate('auth.something_happened'))));
}
}
}
If you do Google -> Facebook it will look like this:
Other way around only Google will be present if your Google email is a trusted email (gmail). More info about that:
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ

The getter 'uid' not defined

i'm trying to create a food track app on android studio, it's my first time and i'm working with firebase_auth 3.3.12. my code in the aut.dart is:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:my_firstapp/models/user_model.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
AuthService();
// create user object based on FirebaseUser.
UserModel _userFromUser(User) {
return user != null ? UserModel(uid: user.uid) : null;
}
// auth change user stream
Stream<UserModel> get user {
return _auth.authStateChanges()
.map(_userFromUser);
}
Future<UserModel> getUser() async {
User user = await _auth.currentUser();
return _userFromUser(user);
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User user = result.user;
return _userFromUser(user);
} catch(e) {
print(e.toString());
return null;
}
}
// sign up with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User user = result.user;
// create a new user document in database
return _userFromUser(user);
} catch(e) {
print(e.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch(e){
print(e.toString());
return null;
}
}
}
However i'm getting 2 errors:
-The getter 'uid' isn't defined for the type 'Stream';
-The expression "await _auth.currentUser()" doesn't evaluate to a function, so it can't be invoked.
How can i rewrite the code? thanks
The _auth.currentUser is not a function (it used to be, but changed about a year ago), but rather a property. It also isn't asynchronous, so you don't need await nor to return a Future.
So:
UserModel getUser() {
User user = _auth.currentUser;
return _userFromUser(user);
}
In this code, your argument is capitalised ('User') but in the code block you write 'user'.
UserModel _userFromUser(User) {
return user != null ? UserModel(uid: user.uid) : null;
}
Furthermore, for _auth.currentUser(), you do not need to use await as it does not return a future.

Endless loop in firestore

I'm trying to update my documents in firestore, so when I'm trying to update it keeps updating without stopping. The first time it updates using the data from the signup dart file, then the second time it updates using the data from another dart file.
Here is the code for the signup:
FirebaseAuth auth = FirebaseAuth.instance;
await auth.createUserWithEmailAndPassword(
email: emailController.text, password: passwordController.text)
.then((value) => {
Navigator.pushNamed(context, 'DialogFlow'),
user=auth.currentUser,
user.sendEmailVerification(),
DatabaseService(uid:user.uid).UpdateUserData("", emailController.text, ChatScreenState().mess)
Here is the code for the other dart file:
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToEnd());
FirebaseAuth auth = FirebaseAuth.instance;
user=auth.currentUser;
DatabaseService db = DatabaseService(uid: user.uid);
return StreamBuilder(
stream: FirebaseFirestore.instance.collection("users").doc(user.uid).snapshots(),
builder: (context , snapshot){
print("====================================");
print(snapshot.data);
print("====================================");
if (snapshot.data != null) {
this.userTestMessage = "";
shhh = pressed ? true : false;
flag = true;
print(Retrieved_messages);
if (Retrieved_messages==false) {
this.messsages = snapshot.data['messsages'];
Retrieved_messages=true;
}
db.UpdateUserData(
user.displayName, user.email, this.messsages);
print(mess);
print(Retrieved_messages);
print("==============================");
print(snapshot.data);
print("==============================");
}
if (db.getUserMessages() == null) {
if (user != null) {
db.UpdateUserData(
user.displayName, user.email, this.messsages);
}
}
And the code for the database which sets and updates the documents is:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:satoshi/models/Userdata.dart';
import 'package:satoshi/widgets/dialog_flow.dart';
class DatabaseService {
//collection reference
final String uid;
List messsages=[];
DatabaseService({this.uid, this.messsages});
final CollectionReference userCollection = FirebaseFirestore.instance
.collection('users');
SetUserData(String Username, String Email,
List messsages) async
{ try {
return await FirebaseFirestore.instance.collection("users").doc(uid).set({
'Username': Username,
'Email': Email,
'messsages': messsages,
}
);
}catch(e){
print(e+" this is the error");
}
}
UpdateUserData(String Username, String Email,
List messsages) async
{ try {
return await FirebaseFirestore.instance.collection("users").doc(uid).update({
'Username': Username,
'Email': Email,
'messsages': messsages,
}
);
}catch(e){
print(e+" this is the error");
}
}
Future getUserMessages() async
{
DocumentSnapshot UserDetail = await userCollection.doc(uid).get();
var msg = UserDetail.data()['messsages'];
return await msg;
}
Stream<QuerySnapshot> get users {
return userCollection.snapshots();
}
Userdata userDataFromSnapshot(DocumentSnapshot snapshot) {
return Userdata(uid: uid,
name: snapshot.get('Username'),
email: snapshot.get('Email'),
messsages: snapshot.get('messsages'),
);
}
Stream<Userdata> get userData {
return userCollection.doc(uid).snapshots().asyncMap(userDataFromSnapshot);
}
}
Note: it keeps adding the data in the signup code, then adds the data in the other dart file, which results in an endless loop, also the snapshot isn't updating, it remains the same data as the signup
You are calling the Update function inside the stream builder so what it basically does is once the update function is called firebase gets notified of the document change and rebuilds the widget so again the update function is called and it turns into an infinite loop. what you can do is add any condition such that it won't get called again once the data is updated.
Example
if (<Somecondition>){
db.UpdateUserData(
user.displayName, user.email, this.messsages);
}

Flutter - Get Firebase custom claims while writing Firebase user to own user instance

I am trying to implement the example given at How do I access custom claims? to my existing code.
I have a Stream which listens to auth changes and updates my own user object with the responded Firebase user. When I store my user object, I would like to get the custom claims of that user as well.
The problem is in _userFromFirebaseUser.
It says "The await expression can only be used in an async function.
Try marking the function body with either 'async' or 'async*'."
But when I do so, the error is hops to my stream where it then says "The argument type 'Future Function(User)' can't be assigned to the parameter type 'User Function(User)'." for "_userFromFirebaseUser" in
// auth change user stream
Stream<local.User> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
Here is my complete authentication class:
import 'package:<my-pckg>/models/user.dart' as local;
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:<my-pckg>/services/database.dart';
//import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
final auth.FirebaseAuth _auth = auth.FirebaseAuth.instance;
// create user obj based on firebase user
local.User _userFromFirebaseUser(auth.User user) {
final isAdmin = (await _currentUserClaims)['admin'] == true;
return user != null
? local.User(
uid: user.uid,
email: user.email,
displayName: user.displayName,
isAdmin: isAdmin)
: null;
}
// auth change user stream
Stream<local.User> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
// sign in anon
Future signInAnon() async {
try {
auth.UserCredential result = await _auth.signInAnonymously();
auth.User user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
print('Successfully logged in, User UID: ${user.uid}');
return user;
} catch (error) {
print(error.toString());
return null;
}
}
// register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
// create a new document for the user with the uid
await DatabaseService(uid: user.uid).updateUserData(null);
print('Successfully registered, User UID: ${user.uid}');
return _userFromFirebaseUser(user);
} catch (error) {
print(error.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
print('User signed out');
return await _auth.signOut();
} catch (error) {
print(error.toString());
return null;
}
}
Future<Map<dynamic, dynamic>> get _currentUserClaims async {
final user = _auth.currentUser;
// If refresh is set to true, a refresh of the id token is forced.
final idTokenResult = await user.getIdTokenResult(true);
return idTokenResult.claims;
}
}
Am I heading into the wrong direction? Is there anything obvious, that I simply do not consider?
Thanks for your help!
For those, heading into the same problem, I found the solution after further research:
You will have to change the .map to .asyncMap.
Here is the code, which works for me:
import 'package:<my-pckg>/models/user.dart' as local;
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:<my-pckg>/services/database.dart';
//import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
final auth.FirebaseAuth _auth = auth.FirebaseAuth.instance;
// create user obj based on firebase user
Future<local.User> _userFromFirebaseUser(auth.User user) async {
final isAdmin = (await _userClaims)['admin'] == true;
return user != null
? local.User(
uid: user.uid,
email: user.email,
displayName: user.displayName,
isAdmin: isAdmin)
: null;
}
// auth change user stream
Stream<local.User> get user {
return _auth.authStateChanges().asyncMap(_userFromFirebaseUser);
}
// sign in anon
Future signInAnon() async {
try {
auth.UserCredential result = await _auth.signInAnonymously();
auth.User user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
print('Successfully logged in, User UID: ${user.uid}');
return user;
} catch (error) {
print(error.toString());
return null;
}
}
// register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
// create a new document for the user with the uid
await DatabaseService(uid: user.uid).updateUserData(null);
print('Successfully registered, User UID: ${user.uid}');
return _userFromFirebaseUser(user);
} catch (error) {
print(error.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
print('User signed out');
return await _auth.signOut();
} catch (error) {
print(error.toString());
return null;
}
}
Future<Map<dynamic, dynamic>> get _userClaims async {
final user = _auth.currentUser;
// If refresh is set to true, a refresh of the id token is forced.
final idTokenResult = await user.getIdTokenResult(true);
return idTokenResult.claims;
}
}
Found here: In flutter, how can I "merge" Firebase onAuthStateChanged with user.getTokenId() to return a Stream?

How would you check whether or not a username is available in Flutter regarding Firebase?

// Checks for username in our registration authentication.
Future<bool> usernameCheck(String username) async {
final result = await FirebaseFirestore.instance
.collection('users')
.where('username', isEqualTo: username)
.get();
return result.docs.isEmpty;
}
This is my function I created to check usernames in Firebase's Firestore database. This function checks to see if a username is available in the database. In the function below, I convert the username to a text string since signupNameController is the textfield for usernames. It doesn't seem to work as exactly as expected.
onPressed: () async {
if (usernameCheck(signupNameController.text) == true) {
showInSnackBar('Username exists. Please try again');
} else if (usernameCheck(signupNameController.text) == false) {
if (signupPasswordController.text != signupConfirmPasswordController.text) {
showInSnackBar("Passwords don't match.");
} else if (signupEmailController.text == ' ' ||
signupPasswordController.text == ' ' ||
signupNameController.text == ' ' ||
signupConfirmPasswordController.text == '') {
showInSnackBar("Please fill out all the required information.");
} else {
try {
UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: signupEmailController.text,
password: signupPasswordController.text,
);
// Our Database collection.
// This will go directly into our Firestore NoSQL database
FirebaseFirestore.instance.collection('users').doc().set({
'username': signupNameController.text.toLowerCase(),
'email': signupEmailController.text,
'password': signupPasswordController.text,
'retypePassword': signupConfirmPasswordController.text
});
Navigator.push(context, MaterialPageRoute(builder: (context) => TabNavigation()));
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
showInSnackBar("The password provided is too weak");
} else if (e.code == 'email-already-in-use') {
showInSnackBar("The account already exists for that email.");
}
} catch (e) {
showInSnackBar(e.toString());
}
}
}
This is what I am trying to accomplish. It doesn't seem to work or input a user. What are some ways to solve this? I am using Flutter and using Firebase for the backend side.

Resources