I have a variable called _memories and I want to update it to the content that is returned from the Firestore database in the .then() and then return it. I know the content is correct in from using the print statement, but neither return statement is returning the updated _memories. It's returning the {'title': 'myTitle'}. Anyone know how to fix this?
List getUserMemories() {
User? currentUser = AuthenticationService().getUser();
if (currentUser == null) {
throw Exception('currentUser is null');
}
CollectionReference memories = _firestore.collection('memories');
List _memories = [
{'title': 'myTitle'}
];
memories
.where('user_email', isEqualTo: currentUser.email)
.get()
.then((QuerySnapshot querySnapshot) async {
_memories = await querySnapshot.docs.map((e) => e.data()).toList();
print("In FirestoreService: $_memories");
return _memories;
})
.catchError((error) => print("Failed to obtain user's memories: $error"));
return _memories;
}
Try to convert getUserMemories to an async function and use await where you call it:
List getUserMemories() async {
User? currentUser = AuthenticationService().getUser();
if (currentUser == null) {
throw Exception('currentUser is null');
}
CollectionReference memories = _firestore.collection('memories');
List _memories = [
{'title': 'myTitle'}
];
try {
final result = await memories
.where('user_email', isEqualTo: currentUser.email)
.get();
_memories = result.docs.map((e) => e.data()).toList();
print("In FirestoreService: $_memories");
} catch (e) {
print("Failed to obtain user's memories: $e"));
}
return _memories;
}
Related
Here is my stream to get data from firebase inside Flutter
Stream<List<TodoModel>> todoStream(String uid) {
return _firestore
.collection("users")
.doc(uid)
.collection("todos")
.orderBy("dateCreated", descending: true)
// .where("done", isEqualTo: true)
.snapshots()
.map((QuerySnapshot query) {
List<TodoModel> retVal = [];
for (var element in query.docs) {
retVal.add(TodoModel.fromDocumentSnapshot(element));
}
return retVal;
});
Here is homeController
Rxn<List<TodoModel>> todoList = Rxn<List<TodoModel>>();
var selectedDate = DateTime.now().obs;
List<TodoModel>? get todos => todoList.value;
#override
void onInit() {
String uid = Get.find<AuthController>().user.value?.uid ?? ' ';
todoList.bindStream(Database().todoStream(uid));
super.onInit();
}
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 from home view, I called to get data from firestore.
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...");
}
},
),
And I get the data for specific users but only the first time when I open my app. When I tried to log out and log in with a new user I get the data from the previous user. I checked and it's not a problem with SignOut functions from firestore, I think it's a problem with reactive snapshot because I got the right snapshot for a specific user but only if I restarted my app and try to log in. So can someone help with this problem?
without seeing the AuthController difficult to tell.
Get.find<AuthController>().user.value?.uid ?? ' ';
You can replace this with FirebaseAuth.instace.currentUser.uid
I have a model and I want to use my services file to fill it from Firebase but I don't know how to do that ?
I am filling it with FutureBuilder that's okey. But it is exhausting me.
Here is my model:
class ReviewModel {
String? uid;
String? userID;
String? comment;
dynamic rate;
ReviewModel({
this.uid,
this.userID,
this.comment,
this.rate,
});
Map<String, dynamic> toMap() {
return {
'uid': uid,
'userID': userID,
'comment': comment,
'rate': rate,
};
}
factory ReviewModel.fromMap(Map<String, dynamic> map) {
return ReviewModel(
uid: map['uid'],
userID: map['userID'],
comment: map['comment'],
rate: map['rate'],
);
}
factory ReviewModel.fromDatabase(
DocumentSnapshot snapshot, Map<String, dynamic> map) {
return ReviewModel(
uid: snapshot['uid'],
userID: map['userID'],
comment: map['comment'],
rate: map['rate'],
);
}
}
Code is Following below,
Future<ReviewModel> getSalonReviews(String salonUID) async {
CollectionReference aRef = FirebaseFirestore.instance
.collection("salons")
.doc(salonUID)
.collection('bucket')
.doc('reviewbox')
.collection('reviews');
dynamic _doc;
var snapshot;
try {
await aRef.get().then((querySnapshot) => {
for (var dummyDoc in querySnapshot.docs)
{
_doc = dummyDoc.data(),
print(_doc),
}
});
return ReviewModel.fromMap(_doc);
} on FirebaseException catch (e) {
Get.snackbar("Hata", e.code);
rethrow;
}
}
This code is not returning my ReviewModel.
Also I am using GetX and this is my GetX code:
final Rx<ReviewModel> _reviewModel = ReviewModel().obs;
ReviewModel get reviewModel => _reviewModel.value;
set reviewModel(ReviewModel value) => _reviewModel.value;
Future fillReviewModel(String uid) async {
SalonController.instance.reviewModel =
await FakeService().getSalonReviews(uid);
}
it return me this:
And this is my Firebase docs:
How do I achive my ReviewModel with Obx. If I try it, it returns null.
You don't have to return a model you'll do something like this in your prvoider file:
List _reviews = [];
List get reviews => [..._reviews];
// IN your future void function
Future<void> myFunction () async{
myReviews = ...result of forEach;
// now update _reviews
_reviews = [...myReviews];
//And then notify listeners
notifylisteners;
}
And then in your futurebuilder
FutureBuilder(future: Provider.of<myClass>(context, listen:false).myFunction(),
builder:(context, snapshot){
// check the state like the following
if(snapshot.connectionState == ConnectionState.done){
final myValues = Provider.of<myClass>(context, listen:false).reviews;
...do something
return your_values}
if(snapshot.connectionState == ConnectionState.waiting){return progressIndicator}
})
In this method I create a new collection("workouts") the first time a set(exercise set) is saved. A workout will have multiple exercises. So how do I add the next exercises to the same workout?
_saveExerciseSetForUser() {
if (currentUser != null) {
FirebaseFirestore.instance
.collection('users')
.doc(currentUser!.uid)
.collection("workouts")
.doc()
.collection("exercises")
.doc(exerciseId)
.collection("sets")
.doc()
.set({
"setNo.": setCount,
"weight": weightController.text,
"reps": repsController.text,
"toFailure": false
}).then((value) => {
});
}
}
In order to add new exercises to existing workouts, you'll need to keep track of the doc or it's ID for your workout. Once you have the workout ID, you will know the path that you can add the new exercise to. users/$userId/workouts/$workoutId/exercises/$exerciseId/sets
Future<void> _saveExerciseSetForUser([String? workoutId]) async {
if (currentUser == null) {
return;
}
final data = {
"setNo.": setCount,
"weight": weightController.text,
"reps": repsController.text,
"toFailure": false,
};
final workoutDoc = FirebaseFirestore.instance
.collection('users')
.doc(currentUser!.uid)
.collection("workouts")
// if workoutId is null, this will create a new workout
.doc(workoutId);
// save doc id somewhere
final workoutDocId = workoutDoc.id;
await workoutDoc.collection("exercises")
.doc(exerciseId)
.collection("sets")
.doc()
.set(data);
}
I have a stream of posts that works fine but I want to check whether the current user likes a particular post. To implement the latter, I have to get info from Firestore which is a future. I have tried using yield* but it doesn't work. My code is as below;
CollectionReference posts = FirebaseFirestore.instance.collection('posts');
String userId = FirebaseAuth.instance.currentUser?.uid;
Stream<List<Post>> get allPosts async* {
yield* posts.snapshots().map((snapShot) => snapShot.docs.map((document) {
DocumentSnapshot isLikedByCurrentUser = await FirebaseFirestore //The await here is erroneous and doesnt work
.instance
.collection('likes')
.doc(this.userId + '_' + document.id)
.get();
return Post.fromJson(
document.id, document.data(), isLikedByCurrentUser.exists);
}).toList());
}
This is more complicated than it needs to be. I'd unwrap the map calls to for loops:
Stream<List<Post>> get allPosts async* {
for (var snapshot in posts.snapshots()) {
var element = <Post>[];
for (var document in shapshot.docs) {
DocumentSnapshot isLikedByCurrentUser = await FirebaseFirestore
.instance
.collection('likes')
.doc(this.userId + '_' + document.id)
.get();
element.add(Post.fromJson(
document.id, document.data(), isLikedByCurrentUser.exists));
}
yield element;
}
}
You can even use a list literal, if you don't mind inlining the isLikedByCurrentUser variable:
Stream<List<Post>> get allPosts async* {
for (var snapshot in posts.snapshots()) {
yield <Post>[
for (var document in shapshot.docs)
Post.fromJson(
document.id,
document.data(),
(await FirebaseFirestore.instance
.collection('likes')
.doc(this.userId + '_' + document.id)
.get())
.exists)
];
}
}
This is how I have re-written it based on this answer by jamesdlin.
Future<List<Post>> _postFromSnapshot(QuerySnapshot snapshot) async {
var futures = snapshot.docs.map((doc) async {
DocumentSnapshot isLikedByCurrentUser = await FirebaseFirestore.instance
.collection('likes')
.doc(this.userId + '_' + doc.id)
.get();
return Post.fromJson(
doc.id, doc.data(), isLikedByCurrentUser.exists);
}).toList();
return await Future.wait(futures);
}
Stream<List<Post>> get allPosts {
return posts.snapshots().asyncMap(_postFromSnapshot);
}
I want to get specific data from a subcollection in Firebase and copy it only once (so no stream) to another collection. How can I fix the code to make this work? Probably I need to change something with the getDocuments() but I don't know how to make this work. Thanks for your help.
void copy() async {
await _firestore
.collection('users')
.document(loggedinUser.uid)
.collection('Dates')
.document(yesterdayDate)
.collection(widget.reference)
.getDocuments().then((QuerySnapshot snapshot) {snapshot.documents.forEach((message) {
final getitem = message.data['item'];
final getarchieved = message.data['archived'];
final getchecked = message.data['checked'];}) {
if (getchecked == false && widget.reference == 'Todo') {
_firestore
.collection('users')
.document(loggedinUser.uid)
.collection('Dates')
.document(selectedDate)
.collection(widget.reference)
.add({
'item': getitem,
'archived': getarchieved,
'checked': getchecked
});
} else if (widget.reference == 'Goals') {
_firestore
.collection('users')
.document(loggedinUser.uid)
.collection('Dates')
.document(selectedDate)
.collection(widget.reference)
.add({
'item': getitem,
'archived': getarchieved,
'checked': false
});
}
}
}
Ok so after a lot of research and trial and error I finally have a solution that works. I hope I can help someone facing the same issue with this solution:
void copy() async {
QuerySnapshot snaphsot = await _firestore
.collection('users')
.document(loggedinUser.uid)
.collection('Dates')
.document(yesterdayDate)
.collection(widget.reference)
.getDocuments();
for (var message in snaphsot.documents) {
final getitem = message.data['item'];
final getarchieved = message.data['archived'];
final getchecked = message.data['checked'];
if (getchecked == false && widget.reference == 'Todo') {
_firestore
.collection('users')
.document(loggedinUser.uid)
.collection('Dates')
.document(selectedDate)
.collection(widget.reference)
.add({
'item': getitem,
'archived': getarchieved,
'checked': getchecked
});
} else if (widget.reference == 'Goals') {
_firestore
.collection('users')
.document(loggedinUser.uid)
.collection('Dates')
.document(selectedDate)
.collection(widget.reference)
.add({'item': getitem, 'archived': getarchieved, 'checked': false});
}
}
}
the solution is great it helped me fix my problem. In my case I wanted a function which allows me to move a document from a collection into another collection.
But I also wanted to delete the old document after copying it into a new collection and didnt want to iterate threw documents. I later on faced a situation, where there was no document with the provided id to get copied, so I had to throw an error for this.
Thats why I decided to post my function here too, so people can choose the relevant parts from Felix and my function containing copy, delete, detect, and error handling for no available document.
moveDocumentToOtherCollection(String docId) async {
var oldColl = db.collection('oldcollection').doc(docId);
var newColl = db.collection('newcollection').doc(oldColl.id);
DocumentSnapshot snapshot = await oldColl.get()
// ignore: missing_return
.then((docSnapshot) {
if (docSnapshot.exists) {
// document id does exist
print('Successfully found document');
newColl
.set({
'name': docSnapshot['name'],
'message': docSnapshot['message'],
'id': docSnapshot['id'],
'isnew': docSnapshot['isnew'],
'dateWhenProofed': DateTime.now(),
'approved': true
})
.then((value) => print("document moved to different collection"))
.catchError((error) => print("Failed to move document: $error")).then((value) => ({
oldColl
.delete()
.then((value) => print("document removed from old collection"))
.catchError((error) {
newColl.delete();
print("Failed to delete document: $error");
})
}));
} else {
//document id doesnt exist
print('Failed to find document id');
}
});
}