error in saving two users chat in flutter - firebase

I was making chat app in flutter with firebase. I was able to save the users chat in firebase by this function:
await FirebaseFirestore.instance
.collection("Chats")
.doc(uid! + FirebaseAuth.instance.currentUser!.uid)
.collection("messages")
.add({
FirebaseAuth.instance.currentUser!.uid:
chatController.text,
}).then((value) => () {
b = uid;
});
but when I save the one users chat. I was thinking that when I will get the chat of second user. It will return a chat from the doc I was saving the both users chat. But unfortunately when I save the users chat it saves a new doc named first users id and then second users id, in first users chat it saves doc named the second user id and first users id. I know what is the reason from which it was happening but how can I resolve that, I mean how can I save the both users chat in one doc
Update: the function I have tried based on Frank's answer:
var docId = uid
.toString()
.compareTo(FirebaseAuth.instance.currentUser!.uid) > 0;
the function used for printing docId:
IconButton(
icon: Icon(Icons.send),
iconSize: 20.0,
onPressed: () async { print(docId);}
)

You need to have a deterministic ID for the document.
One simple way to do this is to always alphabetically order the two UIDs with something like this:
const docId = uid!.compareTo(FirebaseAuth.instance.currentUser!.uid) > 0
? uid! + FirebaseAuth.instance.currentUser!.uid
: FirebaseAuth.instance.currentUser!.uid + uid!
await FirebaseFirestore.instance
.collection("Chats")
.doc(docId)
...
For more on this, see the examples in my answer here: Best way to manage Chat channels in Firebase

Related

Firestore onAuthStateChanged and users in a subcollection

I'm building an app where users can authenticate, in Firestore I save extra data from that user (username, age).
Now in my app, users are coupled to events, I chose to have an events collection, which has a users subcollection.
I'm using the firebase onAuthStateChanged listener to see when my user has logged in. However the issue I'm not facing is, to get the firestore data for my user, I need to know which event this user belongs to, which is of course, data I do not have access to at the time the user signs in, for example:
const onAuthStateChangedPromise = new Promise((resolve, reject) => {
auth.onAuthStateChanged(async firebaseUser => {
if (firebaseUser !== null) {
const user = await getDoc(doc(db, 'events/${eventId}/users', id))
useAuth().user = user
return resolve(user)
}
return resolve(null)
}, err => {
reject(err)
})
})
In the example above, to get my user's data, I need to know the eventId, which I can not possible determine from the authenticated user.
I'm wondering how to achieve this?
I could save the eventId in localStorage as soon as the user registers, but that can cause issue's, since the complete app then relies on something being set on localStorage
The typical way to solve this would be to add the UID of the user in a field inside the events/${eventId}/users documents and then use a collection group query across all users collections. This will give you a list of all event/users docs for that user.
To find the event for such an event/user doc, you first take the DocumentReference for the DocumentSnapshot and then go up the parent chain twice to get to the parent event document.

Realtime data from firestore flutter not working

I was following this tutorial on how to get realtime updates from flutter firestore, https://medium.com/firebase-tips-tricks/how-to-use-cloud-firestore-in-flutter-9ea80593ca40 and I scrolled down to Listen For Realtime Updates section and when I followed the tutorial, this is what I came up with,
String name = 'name here';
String children = 'children here';
String docId = '0';
#override
void initState() {
getUsers();
super.initState();
}
getUsers() async {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final uid = user!.uid;
FirebaseFirestore.instance
.collection("userNames")
.where("uid", isEqualTo: uid)
.snapshots()
.listen((result) {
result.docs.forEach((result) {
print(result["firstName"]);
print(result["children"].toString());
name = result["firstName"];
children = result["children"].toString();
});
});
}
When I print the values to the console they update in realtime, but when I put them in variables and concatenate them into a Text widget like this:
Text('Children: $children'), //It does not update in realtime.
For instance, if in my document if I have children: 3 and I display in in my app, it shows 3, but when I manually change it, it does not update in realtime, I have to press hot reload. Another issue is that I have to initialize the variable before using them in the function, up ahead in the first 3 lines of code. When I hot restart, it shows the values of what I use to initialize them. For where it should show children, it says 'children here' and for where the name is, it puts 'name here', only when I hot reload the page, do the actual firestore values get inputed into them and show data from the firestore database. If there is a solution to any of these problems, I would much prefer an answer in code instead of a link or a brief explanation, I spend hours before I find a piece of code that utilizes the explanation. Thank you
I use snapshots().listen() to listen to change. Then I use ValueNotifier to notify the UI.
final itemsNotifier = ValueNotifier<List<Item>>([]);
FirebaseFirestore.instance
.collection("userNames")
.where("uid", isEqualTo: uid)
.snapshots()
.listen((event) {
itemsNotifier.value = event.docs
.map((doc) => Item.fromSnapshot(
doc as DocumentSnapshot<Map<String, dynamic>>))
.toList();
itemsNotifier.notifyListeners();
});
Since the data is loaded asynchronously, the data isn't available when Flutter first paints your Text widget. You'll need to tell Flutter that it has to repaint the UI when the data is available.
There are two common ways to do this:
Put the children variable in the state of your widget by calling setState(). This will tell Flutter to repaint the widget, and your text will then show the value.
You can also use a StreamBuilder widget, which does the above too - but it also handles all kinds of error states automatically.
I recommend reading about stateful widgets and setState and about the StreamBuilder class to learn more.

Flutter - How to add a field to all documents in firebase collection

I am using flutter firebase. And I want a query to add a key and value to all documents in firebase collection.
I try to use batch write but it add a new documents having field. But not merge to existing documents.
var db= Firestore.instance();
var batch = db.batch();
batch.setData(
db.collection("users").document(),
{"status": "Approved"}
);
When I try to give document Id like document('id') it add only to that document.
I try many and watches YouTube videos but not able find still now. Please help me !
Create a dummy button on one of the pages in your app. Pressing the button should add the field in all documents of the particular collection.
Here, I have used an IconButton to add an empty field called 'bio' in the 'users' collection.
You can delete the button later after you've added the field in your documents because this is your (developer's) problem, and your app user does not need the button.
IconButton(
onPressed: () {
FirebaseFirestore.instance
.collection('users')
.get()
.then(
(value) => value.docs.forEach(
(element) {
var docRef = FirebaseFirestore.instance
.collection('users')
.doc(element.id);
docRef.update({'bio': ''});
},
),
);
},
icon: Icon(Icons.done),
),
This is because you're using the setData() method, instead you should use the updateData() to update:
var db= Firestore.instance();
var batch = db.batch();
batch.updateData(
db.collection("users").document(),
{"status": "Approved"}
);

How to get subcollection data from firebase?

Im trying to calculating some data together . What I have is a videos collection and then each video has a doc id and and then some field for example the uid of the user that uploads this video. And also every video has a sub collection named uservotes. Inside their I saved user voting of this video. This is how It looks
And what I want is getting of one user the user votes rating field calculating together .
HERes how I get for one video the rating maybe that will help to understand
FutureBuilder(
future: firebase,
builder:
(BuildContext context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: Text("loading..."));
} else {
double summe = 0.0;
final docs = snapshot.data.docs;
for (int i = 0;
i < docs.length;
i++) {
summe += (docs[i]['rating']);
print(summe);
}
final possiblestars = docs.length * 5;
print(possiblestars);
return Text(
"${summe.toInt()}/${possiblestars}",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18),
);
}
}),
The firebase stream is this one
var firebase = FirebaseFirestore.instance
.collection('videos')
.doc(videos.data()['id'])
.collection('uservotes')
.get();
So instead of getting all user votes of one video I wanna get all uservotes of all videos form the same user. You can see that each video in videos collection has uid field and thats the uid of the user that upload the video I can get the current user id with that
final uid= FirebaseAuth.instance.currentUser.uid;
And every video that has saved this uid as uid value Iinside field "uid" in videoscollction is one off the videos that I needed
. Then I wanna get of each video the sub collection uservotes all rating to calculating they together . Dont get confused about the same uid doc inside user votes collection thats because the user that uploads this current video also rate his own video .
Hope anyone can help .
For your requirement, with your data model, you would need to query all the videos docs with the same uid, and then, for each doc, query all the doc of the uservotes subcollection.
You could simplify this queries by adding the video owner uid to the uservotes docs and use a collectionGroup query.
HOWEVER, it is not recommended at all to query an entire (sub)collection each time you want to get the number of documents, because you are billed for each document that you read. So it can very fast generate an important bill...
You should maintain a counter for each user (and maybe for each video?). For that the best is to use distributed counters. I would advise to use Cloud Functions to update the counters, this way end-users cannot modify the counters.
Unfortunately, it's impossible in a single query. You should get all videos firstly and then make a new call for each one to get subcollection.

Flutter - How to add Firebase-Auth user credentials to new records (FireStore documents)?

I'm trying to create a simple Crud app with Flutter and Firebase which the records (documents created in FireStore) are related to the user who has been Authenticated. Therefore the Crud functions will only be performed by the user who created the record. IE a user will only be able able to edit/update/delete the records they added in the first place.
I have the firebase_auth and crud functions working nicely with firestore. the issues i'm have is with relating the two. I have chosen to use the users email and the unique identifier (i'm not sure if it's better to use the auto generated user id or not). I have created a separate function for simply returning the current user's email as it's being added to the firestore document. The problem is the first time i add a record the user email returns null, If i submit the form again it starts working fine.
String _userEmail;
_getUserAuthEmail() {
FirebaseAuth.instance.currentUser().then((user){
setState((){this._userEmail = user.email;});
});
return this._userEmail;
}
Which is being called from the onPressed event
onPressed: () {
crudObj.addData({
'itemName': this.itemName,
'userEmail': _getUserAuthEmail(),
}).then((result) {
dialogTrigger(context);
}).catchError((e) {
print(e);
});
},
As i'm just starting out please let me know if there is a better approach. Cheers.
You are getting null because you are not waiting for the currentUser method to settle. Change the _getUserEmail method like this:
String _userEmail;
_getUserAuthEmail() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
_userEmail = user.email;
});
return this._userEmail;
}
Also, about this
"I have chosen to use the users email and the unique identifier (i'm not sure if it's better to use the auto generated user id or not)."
I suggest you using the user's uid for saving user related stuff.

Resources