Running Async Code in factory Function in Flutter / Dart - firebase

I have this function using factory, (which to be honest I'm not sure why is needed, I just got it from a Tutorial, but is working the rest of the code).
This is the Function in question:
factory Listing.fromJason(Map<String, dynamic> data, String id) {
List<String> images = [];
data['photos'].forEach((photo) {
images.add(photo);
});
return Listing(
id: id,
photos: images,
price: double.parse(data['price'].toString()),
status: data['status'],
street: data['street'],
street2: data['street2'],
city: data['city'],
state: data['state'],
zipCode: int.parse(data['zipCode'].toString()),
bedRooms: data['bedRooms'],
bathRooms: data['bathRooms'],
lotSize: data['lotSize'],
schoolDistric: data['schoolDistric'],
taxes: double.parse(data['taxes'].toString()),
homeFeatures: data['homeFeatures'],
floorPlans: data['floorPlans'],
propertySurvey: data['propertySurvey'],
yearBuilt: data['yearBuilt'],
listingAgentName: data['listingAgentName'],
listingAgentEmail: data['listingAgentEmail'],
listingAgentPhone: data['listingAgentPhone'],
dayStore: DateTime.parse(data['dayStore'].toDate().toString()),
downPayment: data['downPayment'],
county: data['county'],
url: data['url'],
listingType: data['listingType'],
name: data['name'],
username: data['username'],
email: data['email'],
imageUrl: data['image_url'],
//isFavorite: favStat,
);
}
The Problem that I'm having is that I need to call inside of it this function to evaluate the Favorite Status:
Future<bool> isItFav(docId) async {
final user = FirebaseAuth.instance.currentUser;
final uid = user.uid;
DocumentSnapshot favoriteSnapshot = await FirebaseFirestore.instance
.doc('userfavorites/$uid/favorites/$docId')
.get();
bool result = favoriteSnapshot.exists;
return result;
}
The problem is that inside that function the one with the factory doesn't allow me to leave it as async or to put an await.
And I need to evaluate with the value of id (which I get as a parameter on top) if the document for that collection (the one on the isItFav function) exists and with that bool add it to the Listing object that I'm returning.
Any Ideas on what I can do.

Just use a static method instead of a named factory constructor. A factory constructor offers no significant advantages in this case.

Related

how to access a collection inside a firestore document and assign it as a list to my dart list inside my model?

how can i access a firestore collection inside a document and assign it to a list in my model, i tried accessing it like this snap.reference.collection('submittedUsers').get(); but i can't use async/await in constructors so i didn't knew what to do, this my model code:
final String fileUrl;
final String title;
final String description;
final List<String> submittedUsers;
LectureModel({
#required this.fileUrl,
#required this.title,
#required this.description,
this.submittedUsers,
}) : super(
fileUrl: fileUrl,
title: title,
description: description,
submittedUsers: submittedUsers,
);
Map<String, dynamic> toDocument() {
return {
'fileUrl': fileUrl,
'title': title,
'description': description,
};
}
factory LectureModel.fromSnapshot(DocumentSnapshot snap) {
// my submittedUsers collection is inside this `snap` document
// i want to get that collection and i assign it's memebers to my model submittedUsers list
final data = snap.data();
return LectureModel(
fileUrl: data['fileUrl'] as String,
title: data['title'] as String,
description: data['description'] as String,
);
}
}
Your data class looks just fine., but in your current structure, also add a list to your factory in the model, like this:
factory LectureModel.fromSnapshot(DocumentSnapshot snap, List<String> submittedUsersList) {
final data = snap.data();
return LectureModel(
fileUrl: data['fileUrl'] as String,
title: data['title'] as String,
description: data['description'] as String,
submittedUsers: submittedUsersList,
);
}
}
But you need to call your method LectureModel.fromSnapshot inside a function, or a future builder or stream builder for example. And also fetch the subcollection after getting the parent document
For example, you need a function like this, and put it in your widget where you need it.
Future<List<LectureModel>> getLectures() async {
QueryDocumentSnapshot snap = await FirebaseFirestore.instance.collection('NAME_OF_PARENT_COLLECTION').get();
List<LectureModel> lectureList=[];
//this will check that there actually is documents in firebase
if(snap.docs.isNotEmpty){
for(var singleSnapDocument in snap.docs){
//then you have to get the subcollection seperately for every
//document.
List<String> listOfsubmittedUsers =[];
listOfsubmittedUsers = await
FirebaseFirestore.instance.collection('NAME_OF_PARENT_COLLECTION')
.doc(singleSnapDocument.id).collection('submittedUsers')
.get().then((result)=> result.docs.map((e) => e.data().toString()).toList());
//this will add a LectureModel object into our list lectureList
lectureList.add(LectureModel.fromSnapshot(singleSnap, listOfsubmittedUsers));
}
}
print('Length of lectureList is: ' + lectureList.length.toString());
return lectureList;
}
Now, anywhere in your code, you can use onPressed or in initState and just call your function getLectures. i.e
onPressed: () async {List<LectureModel> listOfLecture = await getLectures();}
Your problem should be solved.

I want to get document id from my firestore

I can't get document id from firestore check out the screenshot, you will understand better.
Future updateUserData(String name, String address, String description,
String image, String rating) async {
return await collectionReference.document(uid).setData({
'name': name,
'address': address,
'description': description,
'image': image,
'rating': rating,
'berberid' : ######, // what should I write here
});
}
Instead of null I want to pass that id
Couple of method I already tried
This is returning null
String docid() {
String id;
collectionReference.getDocuments().then((value) => {
value.documents.forEach((element) {
id = element.documentID;
}),
});
return id;
}
This returns the name of collection, such as berbers
documentReference.documentID
Data is loaded from Firebase asynchronously. That means that in this code:
String id;
collectionReference.getDocuments().then((value) =>{
value.documents.forEach((element) {
id= element.documentID;
}),
});
return id;
}
By the time the return id statement executes, the id= element.documentID hasn't run yet. You can most easily check this for yourself by putting breakpoints on these lines and running the code in the debugger, or by adding some log statements:
print("Before getDocuments()");
collectionReference.getDocuments().then((value) =>{
print("Got documents");
});
print("Aftter getDocuments()");
When you run this code, it prints:
Before getDocuments()
After getDocuments()
Got documents
This is likely not what you expected, but does explain why you get null from the function: the data hasn't loaded yet by the time your function exits.
This is completely normal when dealing with asynchronous operations, which is pretty much any modern web/cloud API. So you'll have to deal with it in your code.
The quickest way to do that is to move the code that needs the data into the then() callback that gets the data. So instead of return id you actually put the code that uses the id into the method:
collectionReference.getDocuments().then((value) =>{
String id;
value.documents.forEach((element) {
id= element.documentID;
}),
print(id); // use the id here
});
This could also be a call to your updateUserData function.
Alternatively, you can use async / await and return a Future from your function:
Future<string> getDocumentId() async {
String id;
var value = await collectionReference.getDocuments();
value.documents.forEach((element) {
id= element.documentID;
}),
return id;
}
To call this function you then do:
getDocumentId().then((id) =>{
print(id); // use id here
})
Or you can use another await and do:
var id = await getDocumentId()
print(id);
Here's what I think you should do, you add the documentID to barberid when you create the document instead of updating it which is weird how the user know ?. Here's how you do it:
await Firestore.instance.collection("your collection name").add({
'name': name,
'address': address,
'description': description,
'image': image,
'rating': rating,
}).then((value) => Firestore.instance
.document(value.path)
.updateData({"barberid": value.documentID}));
value there is DocumentReference that's how you get the documentID and then you can update it with Firestore.instance.document("document path") fill the document path with value.path, and update the barberid with documentID should work just fine.

Convert FirebaseUser to a customized User class in a Stream

I want to convert a FirebaseUser to a customized class by adding some extra fields in a Flutter project which is using Firebase as backend. Here is my code:
Stream<User> get user {
// return _auth.onAuthStateChanged.map(_fromFirebaseUser);
final theUser = _auth.onAuthStateChanged.map((firebaseUser) {
final result = Firestore.instance.collection("users")
.document(firebaseUser.uid).snapshots().map((snapshot) {
return User(
uid: user.uid,
name: snapshot.data['name'],
email: user.email,
age: snapshot.data['age'],
gender: snapshot.data['gender']
);
}
return result;
});
return theUser;
}
The basic idea is I will get the data from users collection and populate the User model. But I got the following error message:
The argument type 'Stream' can't be assigned to the parameter type 'Stream'.
Need your advice on how to return a Stream<User> instead of Stream<Stream<User>>. Thanks.
In this case, you're using 2 streams: _auth.onAuthStateChanged and Firestore.instance.collection("users").document(firebaseUser.uid).snapshots(). You need to either combine it, or simply asynchronously get the user document from the collection instead of listening for a stream:
Stream<User> get user {
return _auth.onAuthStateChanged.asyncMap((firebaseUser) async {
final snapshot = await Firestore.instance
.collection("users")
.document(firebaseUser.uid)
.get();
return User(
uid: firebaseUser.uid,
name: snapshot.data['name'],
email: firebaseUser.email,
age: snapshot.data['age'],
gender: snapshot.data['gender']
);
});
}
Note the using of asyncMap instead of map to allow asynchronous document fetch.

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.

A simple Query in flutter/firebase database

I try to experience Firebase Live database with flutter.
I just would like to get a value in the datasnapshot of the firebase response.
My Firebase
My Code
static Future<User> getUser(String userKey) async {
Completer<User> completer = new Completer<User>();
String accountKey = await Preferences.getAccountKey();
FirebaseDatabase.instance
.reference()
.child("accounts")
.child(accountKey)
.child("users")
.childOrderBy("Group_id")
.equals("54")
.once()
.then((DataSnapshot snapshot) {
var user = new User.fromSnapShot(snapshot.key, snapshot.value);
completer.complete(user);
});
return completer.future;
}
}
class User {
final String key;
String firstName;
Todo.fromJson(this.key, Map data) {
firstname= data['Firstname'];
if (firstname== null) {
firstname= '';
}
}
}
I got Null value for firstname.
I guess I should navigate to the child of snapshot.value. But impossible to manage with foreach, or Map(), ...
Kind regards, Jerome
You are querying with a query and the documentation for Queries (here in JavaScript, but it is valid for all languages), says that "even when there is only a single match for the query, the snapshot is still a list; it just contains a single item. To access the item, you need to loop over the result."
I don't know exactly how you should loop, in Flutter/Dart, over the children of the snapshot but you should do something like the following (in JavaScript):
snapshot.forEach(function(childSnapshot) {
var childKey = childSnapshot.key;
var childData = childSnapshot.val();
// ...
});
and assuming that your query returns only one record ("one single match"), use the child snapshot when you do
var user = new User.fromSnapShot(childSnapshot.key, childSnapshot.value);
This will give you Users in reusable dialog. There might be slight disservice to yourself if you don't use stream and stream-builders, the solution below is a one time fetch of the users' collection on FirebaseDB.
class User {
String firstName, groupID, lastName, pictureURL, userID;
User({this.firstName, this.groupID, this.lastName, this.pictureURL, this.userID});
factory User.fromJSON(Map<dynamic, dynamic> user) => User(firstName: user["Firstname"], groupID: user["Group_id"], lastName: user["Lastname"], pictureURL: user["Picturelink"], userID: user["User_id"]);
}
Future<List<User>> users = Firestore.instance.collection("users").snapshots().asyncMap((users) {
return users.documents.map((user) => User.fromJSON(user.data)).toList();
}).single;

Resources