How to match Firebase User UID with Firestore Doc ID - Flutter - firebase

I am new to flutter. So Please kindly bear with me.
I am creating a Real Estate App using Firebase and Provider.
I have two root Collections (1) "users" and (2) "properties"
I would like to achieve one specific task.
Although it seems like a simple task, yet I am still unable to solve it.
The task is to store Firebase User UID as Firestore Document ID of the rool Collection called "users" when the user sign up.
The problem I am having is the Firestore Document ID is automatically generated and its not Firebase User UID. Plus the field for userId appears null in Firestore.
Please see the screenshot of my database here
Thank you for your attention and time.
Here is my user_model.dart
class NayyaaUser {
String? uid;
String name;
String email;
String? phone;
NayyaaUser({this.uid, required this.name, required this.email, this.phone});
//send data to Firestore
Map<String, dynamic> toMap() {
return {
'userId': uid,
'userName': name,
'userEmail': email,
'userPhone': phone,
};
}
//draw data from firestore
factory NayyaaUser.fromFirestore(Map<String, dynamic> firestore) =>
NayyaaUser(
uid: firestore['userId'],
email: firestore['userEmail'] ?? " ",
name: firestore['userName'] ?? " ",
phone: firestore['userPhone'] ?? " ",
);
}
Here is my user_provider.dart
class UserProvider extends ChangeNotifier {
final firestoreService = FirestoreService();
final authService = AuthService();
String? _userId;
String? _name;
String? _email;
String? _phone;
//getters
String? get userId => _userId;
String? get name => _name;
String? get email => _email;
String? get phone => _phone;
//setters
changeName(String value) {
_name = value;
notifyListeners();
}
changeEmail(String value) {
_email = value;
notifyListeners();
}
changePhone(String value) {
_phone = value;
notifyListeners();
}
saveUserProfile() {
if (_userId == null) {
var updateUserProfile = NayyaaUser(
// userId: _userId,
uid: _userId,
name: name ?? '',
email: email ?? '',
phone: phone ?? '');
firestoreService.saveUserDataToFirestore(updateUserProfile);
} else {
var newUserProfile = NayyaaUser(
// userId: _userId,
uid: _userId,
name: name ?? '',
email: email ?? '',
phone: phone ?? '');
firestoreService.saveUserDataToFirestore(newUserProfile);
}
}
}
Here is auth_service.dart
class AuthService {
final FirebaseAuth _authInstance = FirebaseAuth.instance;
//create user obj based on "User" from Firebase
NayyaaUser? _userFromFirebase(User? user) {
return user != null
? NayyaaUser(
uid: user.uid,
name: '',
email: '',
phone: '',
)
: null;
}
// auth change user stream
Stream<NayyaaUser?> get userAuthStatus {
return _authInstance.authStateChanges().map(_userFromFirebase);
}
// sign in with email + password
Future signIn(String email, String password) async {
try {
UserCredential userAuthResult = await _authInstance
.signInWithEmailAndPassword(email: email, password: password);
User? user = userAuthResult.user;
return _userFromFirebase(user!);
} catch (e) {
// ignore: avoid_print
print(e.toString());
return null;
}
}
Future signUp(String email, String password) async {
try {
UserCredential userAuthResult = await _authInstance
.createUserWithEmailAndPassword(email: email, password: password);
User? user = userAuthResult.user;
return _userFromFirebase(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
return await _authInstance.signOut();
} catch (e) {
// ignore: avoid_print
print(e.toString());
return null;
}
}
}
Here is firestore_service.dart
class FirestoreService {
final CollectionReference _userRef =
FirebaseFirestore.instance.collection('users');
final CollectionReference _propertyRef =
FirebaseFirestore.instance.collection('properties');
//add or update user to firestore
Future<void> saveUserDataToFirestore(NayyaaUser nayyaaUserData) {
return _userRef.doc(nayyaaUserData.uid).set(nayyaaUserData.toMap());
}
// fetch user data from firestore
Stream<List<NayyaaUser>> getNayyaaUser() {
return _userRef.snapshots().map((snapshot) => snapshot.docs
.map((document) =>
NayyaaUser.fromFirestore(document.data() as Map<String, dynamic>))
.toList());
}
}

After login/signup successfully you can get user id from FirebaseAuth.instance.currentUser?.uid

Related

A document path must be a non-empty string, Flutter - Firebase error?

I have some mistakes with flutter and firebase, if someone can help would be great here is my auth controller
class AuthController extends GetxController {
final FirebaseAuth auth = FirebaseAuth.instance;
final Rxn<User> _firebaseUser = Rxn<User>();
Rx<XFile>? _pickedImage;
XFile? get profilePhoto => _pickedImage?.value;
// final user = FirebaseAuth.instance.currentUser.obs;
Rxn<User> get user => _firebaseUser;
// final user = FirebaseAuth.instance.currentUser;
#override
onInit() {
_firebaseUser.bindStream(auth.authStateChanges());
super.onInit();
}
// void register(
// String name, String email, String password, XFile? image) async {
// try {
// UserCredential _authResult = await auth.createUserWithEmailAndPassword(
// email: email.trim(), password: password);
// //create user in database.dart
// String downloadUrl = await uploadToStorage(image!);
// UserModel _user = UserModel(
// id: _authResult.user?.uid,
// name: name,
// email: _authResult.user?.email,
// profilePic: downloadUrl,
// );
// if (await Database().createNewUser(_user)) {
// Get.find<UserController>().user = _user;
// }
// } catch (e) {
// Get.snackbar(
// "Error creating Account",
// e.toString(),
// snackPosition: SnackPosition.BOTTOM,
// );
// }
// }
void register(
String name, String email, String password, XFile? image) async {
try {
if (name.isNotEmpty &&
email.isNotEmpty &&
password.isNotEmpty &&
image != null) {
// save out user to our ath and firebase firestore
UserCredential _authResult = await auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
String downloadUrl = await uploadToStorage(image);
UserModel _user = UserModel(
id: _authResult.user?.uid,
name: name,
email: _authResult.user?.email,
profilePic: downloadUrl,
);
if (await Database().createNewUser(_user)) {
Get.find<UserController>().user = _user;
} else {
Get.snackbar(
'Error Creating Account',
'Please enter all the fields',
);
}
}
} catch (e) {
Get.snackbar(
'Error Creating Account',
e.toString(),
);
}
}
void login(String email, password) async {
try {
UserCredential _authResult = await auth.signInWithEmailAndPassword(
email: email.trim(), password: password);
Get.find<UserController>().user =
await Database().getUser(_authResult.user?.uid ?? '');
} catch (e) {
Get.snackbar("About User", "User message",
snackPosition: SnackPosition.BOTTOM,
titleText: Text("Acount creation failed"),
messageText:
Text(e.toString(), style: TextStyle(color: Colors.white)));
}
}
Future<void> signOut() async {
await auth.signOut();
Get.find<UserController>().clear();
}
Future pickImage() async {
print("call on click add photo icon");
final ImagePicker _picker = ImagePicker();
final XFile? pickedImage =
await _picker.pickImage(source: ImageSource.gallery);
print('picked image filled with image from gallery'); //This doesnt print at
if (pickedImage != null) {
Get.snackbar('Profile Picture',
'You have successfully selected your profile picture!');
// print(pickedImage.path);
}
_pickedImage = Rx<XFile>(pickedImage!);
// print(_pickedImage);
// print(profilePhoto);
}
// upload to firebase storage
Future<String> uploadToStorage(XFile? image) async {
Reference ref = FirebaseStorage.instance
.ref('')
.child('profilePics')
.child(auth.currentUser!.uid);
// print(ref);
UploadTask uploadTask = ref.putFile(File(image?.path ?? 'idemo'));
print(uploadTask);
// TaskSnapshot snap = await uploadTask;
String downloadUrl = await (await uploadTask).ref.getDownloadURL();
print(downloadUrl);
return downloadUrl;
}
}
Here is my function to createNewUser
class Database {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<bool> createNewUser(UserModel user) async {
try {
await _firestore.collection("users").doc(user.id).set({
"name": user.name,
"email": user.email,
"profilePhoto": user.profilePic
});
return true;
} catch (e) {
print(e);
return false;
}
}
Here is HomeController
class HomeController extends GetxController {
final Rxn<List<TodoModel>> todoList = Rxn<List<TodoModel>>([]);
var selectedDate = DateTime.now().obs;
List<TodoModel>? get todos => todoList.value;
#override
void onInit() {
super.onInit();
String? uid = Get.find<AuthController>().auth.currentUser?.uid ?? '';
print(uid);
todoList.bindStream(Database().todoStream(uid));
}
chooseDate() async {
DateTime? pickedDate = await showDatePicker(
context: Get.context!,
initialDate: selectedDate.value,
firstDate: DateTime(2000),
lastDate: DateTime(2024),
//initialEntryMode: DatePickerEntryMode.input,
// initialDatePickerMode: DatePickerMode.year,
);
if (pickedDate != null && pickedDate != selectedDate.value) {
selectedDate.value = pickedDate;
}
}
}
and here is View page
GetX<HomeController>(
init: Get.put<HomeController>(HomeController()),
builder: (HomeController todoController) {
if (todoController.todos != null) {
// print(todoController.todos?.done ?? false);
return Expanded(
child: ListView.builder(
itemCount: todoController.todos?.length,
itemBuilder: (_, index) {
return TodoCard(
uid: controller.user.value?.uid ?? '',
todo: todoController.todos![index],
);
},
),
);
} else {
return Text("loading...");
}
},
),
So, I have an error when I register a new user I got this error:
The following assertion was thrown building Builder(dirty):
a document path must be a non-empty string
Failed assertion: line 116 pos 14: ‘path.isNotEmpty’
And here is output from terminal:
The relevant error-causing widget was
GetMaterialApp
lib/main.dart:23
When the exception was thrown, this was the stack
#2 _JsonCollectionReference.doc
#3 Database.todoStream
#4 HomeController.onInit
#5 GetLifeCycleBase._onStart
#6 InternalFinalCallback.call
#7 GetInstance._startController
#8 GetInstance._initDependencies
#9 GetInstance.find
#10 GetInstance.put
#11 Inst.put
So a problem is with this path, and when I reload from the visual studio I god the right user with the right data. So the problem is when I register a user for the first time.
It looks like uid is empty, which you should also be able to see from looking up print(uid); in your output.
When your application or web page loads, Firebase automatically tries to restore the previously signed in user from its local state. This requires that it makes a call to the server however (for example to check if the account has been disabled) and while that call is going on, your main code continues to execute and the currentUser variable is going to be null.
Your code needs to take this into account. The easiest way to do this is to not depend on currentUser, but instead to use an reactively respond to changes in the authentication state as shown in the first example in the documentation on getting the current user:
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user != null) {
print(user.uid);
}
});
The authStateChange method here returns a stream that fires an event whenever the authentication state changes, so when the user signs in or signs out. The common way to use this stream is to either set the user to the state of your widget, or to use the stream directly in a StreamBuilder.

Display User Data From Map String Dynamic Using Provider

I am trying to Display Data on another Page I am Using provider to get the user data and put it to my model like this:
getUserData() async {
User user = FirebaseAuth.instance.currentUser!;
String value = user.uid;
return usersFirebaseReference.doc(value).snapshots().listen((event) {
if (event.data() != null) {
log("$event.data()");
final Map<String, dynamic> _userData = event.data() as Map<String, dynamic>;
userData = UserData.fromMap(_userData);
userData!.firebaseId;
} else {
print('User Data is Empty');
userData = null;
}
notifyListeners();
print('Update gotten from User Data');
});
}
I am getting Nul from the returning data whenever I try to display the data in homepage as such:
userDataCrudService.userData?.store
Please Is there anything I am doing wrong. I am new to flutter and firestore Please. Thank you.
Below is My Model:
class UserData {
bool? admin;
String? firebaseId;
String? username;
String? email;
String? password;
String? name;
String? store;
String? address;
String? phone;
UserData({
this.admin,
this.firebaseId,
this.username,
this.email,
this.password,
this.name,
this.store,
this.address,
this.phone,
});
Map<String, dynamic> toMap() {
return {
'admin': admin,
'firebaseId': firebaseId,
'username': username,
'email': email,
'password': password,
'name': name,
'store': store,
'address': address,
'phone': phone,
};
}
factory UserData.fromMap(Map<String, dynamic> map) {
return UserData(
admin: map['admin'],
firebaseId: map['firebaseId'],
username: map['username'],
email: map['email'],
password: map['password'],
name: map['name'],
store: map['store'],
address: map['address'],
phone: map['phone'],
);
}
String toJson() => json.encode(toMap());
factory UserData.fromJson(String source) =>
UserData.fromMap(json.decode(source));
}
I got it it is a mistake from me, I am trying to get current user id from firestore but it saved with it own document id, I solved it by saving the document id with the user id at signup.

Flutter: _CastError (type 'Null' is not a subtype of type 'Map<String, dynamic>' in type cast)

When I create a new user the error (_CastError (type 'Null' is not a subtype of type 'Map<String, dynamic>' in type cast)) apears at User.fromSnap ...as Map<stringm,dynamic> and firebase creates the user auth but not the user data
Can you help me to add array of array of objects to firebase?
Thanks!
user.dart
class User {
final String uid;
final String name;
final List<Game> game;
User(
{required this.uid,
required this.name,
required this.game});
factory User.fromSnap(DocumentSnapshot snap) {
var snapShot = snap.data() as Map<String, dynamic>;
return User(
uid: snapShot['uid'],
name: snapShot['name'],
game: snapShot['game']);
}
Map<String, dynamic> toJson() => {
'uid': uid,
'name': name,
'game': game,
};
}
class Game{
String rolName;
bool alive;
Game({required this.rolName, required this.alive});
factory Game.fromSnap(Map<String, dynamic> snap) {
return Game(rolName: snap['rolName'], alive: snap['alive']);
}
}
auth.dart
class AuthMethods with ChangeNotifier {
...
Future<String> singUpUser(
{required String email,
required String password,
required String namee}) async {
String res = 'Error';
try {
if (email.isNotEmpty ||
password.isNotEmpty ||
name.isNotEmpty ) {
UserCredential cred = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
model.User _user = model.User(
uid: cred.user!.uid,
name: name,
game: [model.Game(rolName: 'default', alive: false)]);
await _firestore
.collection("users")
.doc(cred.user!.uid)
.set(_user.toJson());
res = 'success';
} else {
res = "Please enter all the fields";
}
} catch (err) {
res = err.toString();
}
return res;
}
Your factory looks right. But you're sending it nothing...the snap is null is what that error is telling you.

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?

Resources