Waiting for result of Future<DocumentSnapshot> in Flutter Firestore - firebase

I have a cloud FireStore database with 2 fields.
imageUrl (url of a remote file)
user (reference field of a document in users collection)
Below is how I get the documents from the images collection.
class ImagePost {
final String imageUrl;
final User user;
const ImagePost(
{this.imageUrl,
this.user});
factory ImagePost.fromDocument(DocumentSnapshot document) {
User userInfo;
DocumentReference userReference = document['user'];
Future<DocumentSnapshot> userRef = userReference.get();
userRef.then((document) {
userInfo = User.fromJSON(document.data);
});
ImagePost post = new ImagePost(
imageUrl: document['imageUrl'],
user: userInfo // ==> always null while returning
);
return post;
}
}
When getting the reference user document, the post object always contains null value for user field. I expect the user object to be populated.
But the user value is retrieved late and not returned along with the post object.
How can I ensure that user value is retrieve before returning the post value?

That's because the get() method return a Future and you need to use async 'await' in order to wait for the response , but is not posible to use it in your constructor.
Just create a method (not constructor) and use like this :
Future<ImagePost> getImagePostFromDocument(DocumentSnapshot document) async {
DocumentReference userReference = document['user'];
DocumentSnapshot userRef = await userReference.get();
User userInfo = User.fromJSON(userRef);
ImagePost post = new ImagePost(
imageUrl: document['imageUrl'],
user: userInfo
);
return post;
}
I recommend you to call it form a FutureBuilder

Related

Flutter firebase get a field from document

I'm trying to get the message from a field in a collection. It is a read only data, i have modeled it like this
class SocialShare {
final String message;
SocialShare({
this.message,
});
factory SocialShare.fromJson(Map<String, dynamic> json) {
return SocialShare(
message: json['message'],
);
}
}
I have a collection named 'Social Share and contains a doc with a single field called message..
Here is how i call it
class SocialShares {
final CollectionReference _socialMessage =
FirebaseFirestore.instance.collection('socialShare');
Future<SocialShare> fetchsocial() {
return _socialMessage.get().then((value) {
return SocialShare.fromJson(value); // how can i call it
});
}
}
How can i get a that value from firebase
You can do fetchSocial async and await the result to return:
fetchSocial() async{
var value = await _socialMessage.get();
return SocialShare.fromJson(value);
}
then you have to call fetchSocial method with await or then where you need it.
await fetchSocial() or fetchSocial.then ...
The value in _socialMessage.get().then((value) { is a QuerySnapshot object, which contains the DocumentSnapshots of all documents in the socialShare collection.
To get a field, or the Map<String, dynamic> of all fields, you need the data from a single document. For example, to get the message field fro the first document from the collection, you can do:
return SocialShare.fromJson(value.docs[0].data());

Flutter - Deleting a document in Firestore leads to the deletion of a random document and not the selected one

The 'utenti' (users) collection contains a document for each user which in turn contains the 'salvataggi' (saves) collection to save objects within the app.
Each document inside 'salvataggi' (saves) has an automatically generated id containing a series of data (String to be precise).
Documents within 'salvataggi' (saves) can be added by saving objects created from two other collections always in FIrestore.
When, through a button, I want to delete an object from the 'salvataggi' (saves) collection, a random document is deleted and not the one corresponding to the object.
Screenshot of Firestore
Object 1
final CollectionReference _usersRef =
FirebaseFirestore.instance.collection('utenti');
final User _user = FirebaseAuth.instance.currentUser;
//Add
Future _addToSaved() {
return _usersRef.doc(_user.uid).collection('salvataggi').doc().set({
'fonte': elenco.fonte,
'title': elenco.title,
'url': elenco.urlAvv,
'imageEv': elenco.urlAvv,
'p1': elenco.iconaProspettiva1,
'p1url': elenco.urlProspettiva1,
});
}
//Remove
Future _removeFromSaved() async {
CollectionReference userSaved =
_usersRef.doc(_user.uid).collection('salvataggi');
QuerySnapshot querySnap = await userSaved.get();
querySnap.docs[0].reference.delete();
}
Object 2
final CollectionReference _usersRef =
FirebaseFirestore.instance.collection('utenti');
final User _user = FirebaseAuth.instance.currentUser;
//Add
Future _addToSaved() {
return _usersRef.doc(_user.uid).collection('salvataggi').doc().set({
'fonte': widget.single.fonte,
'title': widget.single.title,
'url': widget.single.urlAvv,
'imageEv': widget.single.imageEvAvv,
'lastupdate': widget.single.dataAggiornamento,
'p1': widget.single.iconaProspettiva1,
'p1url': widget.single.urlProspettiva1,
});
}
//Remove
Future _removeFromSaved() async {
CollectionReference userSaved =
_usersRef.doc(_user.uid).collection('salvataggi');
QuerySnapshot querySnap = await userSaved.get();
querySnap.docs[0].reference.delete();
}
What am I doing wrong? Why does this happen?
When the user saves a document try saving the id of that document with it so whenever the user unsaved the document. You can pass the id of that unsaved document to firestore.
It will look something like this
Future _removeFromSaved(String docID) async {
CollectionReference userSaved =
_usersRef.doc(_user.uid).collection('salvataggi');
await userSaved.doc(docID).delete()
}
--UPDATE--
You can save document id by calling the then method after adding the document to firestore and then updating it
Future _addToSaved()async {
await _usersRef.doc(_user.uid).collection('salvataggi').add({
'fonte': widget.single.fonte,
'title': widget.single.title,
'url': widget.single.urlAvv,
'imageEv': widget.single.imageEvAvv,
'lastupdate': widget.single.dataAggiornamento,
'p1': widget.single.iconaProspettiva1,
'p1url': widget.single.urlProspettiva1,
}).then(docRef=>await _usersRef.doc(_user.uid).collection('salvataggi').doc(docRef.id).update({'id':docRef.id}));
}
querySnap.documents.first.reference.delete();
Use this instead of querySnap.docs[0].reference.delete();

Update Firestore document where user_id is same as currentUser uid

I have the function that I am using to create or update customer.
I am able to successfully write to db. I created a field called user_id where I save the currentUser uid into so I can read only logged in user documents.
However, I am unable to update the documents because I know I'm probably not referencing the document the right way.
I get the following error:
flutter: Error: PlatformException(Error 5, FIRFirestoreErrorDomain, No
document to update:
What am I doing wrong?
Here's my function below:
Future createOrUpdateCustomer(Customer customer, bool isUpdating) async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
String userId = user.uid;
print('Current logged in user uid is: $userId');
CollectionReference customerRef =
await Firestore.instance.collection('customers');
if (isUpdating) {
customer.updatedAt = Timestamp.now();
customer.userId = userId;
await customerRef.document().updateData(customer.toMap());
print('updated customer with id: ${customer.id}');
print('updated customer with logged in uid: ${customer.userId}');
} else {
customer.createdAt = Timestamp.now();
DocumentReference documentReference = customerRef.document();
customer.id = documentReference.documentID;
customer.userId = userId;
print('created customer successfully with id: ${customer.id}');
await documentReference.setData(customer.toMap(), merge: true);
addCustomer(customer);
}
notifyListeners();
}
You are trying to update a nonexistent document. In this line,
await customerRef.document().updateData(customer.toMap())
You are creating a document reference with a randomly-generated id. You should explicitly set the id of the document you're updating.
I think you can update the document with conditions but the reference must be the doc id, that you see in 2nd section of firestore interface.

How do I make a subcollection of user data in Firebase?

I'm creating an Hospital App in flutter. My goal is to configure firebase in such a way that the root collection is named User/uid(uid which is automatically generated when user is created)/someText/documentID(automatically generated by Firebase). This is my code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:mgm_app/models/vaccList.dart';
class DatabaseService {
final String uid;
DatabaseService({this.uid});
CollectionReference userReg = Firestore.instance.collection('User');
CollectionReference vaccTaken = Firestore.instance.collection('User').document(uid).collection('Vaccine Administered');
Future regUserData(String email) async {
return await userReg.document(uid).setData({
'email': email,
});
}
Future updateUserData(String vaccName,String dateAdmin) async {
return await vaccTaken.document().setData({
'name': vaccName,
'vaccine given': dateAdmin,
}
);
}
When a new user registers, a document with their uid is automatically created in the User collection. But when I'm trying to enter some personal data in the folder User/uid/VaccineAdmnistered,
I am not able to pass the value of the current user uid on this line of code
Firestore.instance.collection('User').document(uid).collection('Vaccine Administered');
The error i'm getting is
Only static members can be accessed in initializers.
You are using the uid inside the method document before the constructor is called. Therefore do the following:
class DatabaseService {
final String uid;
DatabaseService({this.uid});
CollectionReference vaccTaken = Firestore.instance.collection('User');
Future regUserData(String email) async {
return await vaccTaken.document(uid).setData({
'email': email,
});
}
Future updateUserData(String vaccName,String dateAdmin) async {
return await vaccTaken.document(uid).collection('Vaccine Administered').document().setData({
'name': vaccName,
'vaccine given': dateAdmin,
}
);
}
Create a variable vaccTaken which will reference the collection User, then inside the method updateUserData access the uid.

Passing Firebase current user (as Future) as a parameter to a query in a Stream. Is it possible or is there another way?

I have a Stream building a list from a Firebase collection QuerySnapShot. The query and the stream builder work great, if I don't pass variable data to the query (the 'where' statement). However, what I am trying to do is pass the FirebaseAuth.currentUser as a filter in the where clause of my Stream.
I am sure there is something I am not understanding about making these 2 separate async calls.
Basically I need to get the uid of the currently authenticated user and pass that into the query in my stream.
I am super new to Flutter and am on a rapid fast track to get my chops. Been fully immersed for about a week.
class Booking {
final DateTime startTime;
final DateTime endTime;
final String name;
final String bookingId;
final String truckID;
Booking({ this.bookingId, this.truckID, this.startTime, this.endTime, this.name });
}
// build the booking list from the QuerySnapShot
List<Booking> _bookingListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Booking(
bookingId: doc.documentID ?? '',
name: doc.data['name'] ?? '',
startTime: doc.data['startTime'].toDate() ?? '',
endTime: doc.data['endTime'].toDate() ?? '',
truckID: doc.data['truckID']
);
}).toList();
}
//asynchronously get the uid of the currentuser from FirebaseAuth
Future<String> inputData() async {
final FirebaseUser _aUser = await FirebaseAuth.instance.currentUser();
final String _uid = _aUser.uid.toString();
return _uid;
}
Here is where I am trying to pass the current user into the Stream
//get user specific booking stream
Stream<List<Booking>> get bookings {
final _myUserId = inputData();
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //Instance of 'Future<String>'...needs to be the uid of the current user.
.snapshots()
.map(_bookingListFromSnapshot);
}
// the widget consuming the list
class _BookingListState extends State<BookingList> {
#override
Widget build(BuildContext context) {
final bookings = Provider.of<List<Booking>>(context) ?? [];
return ListView.builder(
itemCount: bookings.length,
itemBuilder: (context,index){
return BookingTile(booking: bookings[index]);
},
);
}
}
EDIT to include the Stream usage for feedback (after wrapping the Stream in a Future as suggested)
In my home.dart file I listen for the Stream<List<Booking>>> so I can build the list of bookings that are displayed on that page. In this next block I now get an error that I cannot assign the parameter type Stream<List<Booking>> to the argument type Future<Stream<List<Booking>>>. The compiler suggests changing the parameter type or casting the argument to <Stream<list<Booking>>
The full compile message
lib/screens/home/home.dart:38:32: Error: The argument type 'Future<Stream<List<Booking>>>' can't be assigned to the parameter type 'Stream<List<Booking>>'.
- 'Future' is from 'dart:async'.
- 'Stream' is from 'dart:async'.
- 'List' is from 'dart:core'.
- 'Booking' is from 'package:models/booking.dart' ('lib/models/booking.dart').
Try changing the type of the parameter, or casting the argument to 'Stream<List<Booking>>'.
value: DatabaseService().bookings,
home.dart
return StreamProvider<List<Booking>>.value(
value: DatabaseService().bookings,
child: Scaffold( ... ) //Scaffold
); //StreamProvider.value
I have tried changing either the parameter value or the argument DatabaseService().value to the suggested types...and I have failed :)
modified bookings getter after I changed it based on feedback
//get user specific booking stream
Future<Stream<List<Booking>>> get bookings async {
final _myUserId = await inputData();
print(_myUserId);
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //here is where I want to pass the currentUser
.snapshots()
.map(_bookingListFromSnapshot);
}
Yes, you can use and await futures only inside async function. So first change your bookings getter as follows.
//get user specific booking stream
Future<Stream<List<Booking>>> get bookings {
final _myUserId = await inputData();
return bookingCollection
.where("truckID", isEqualTo: _myUserId) //Instance of 'Future<String>'...needs to be the uid of the current user.
.snapshots()
.map(_bookingListFromSnapshot);
}
So, where you are providing this stream, you need to provide Future then only you can get stream from Future.

Resources