I have this method the get all my documents from a given collection:
getData() async {
await databaseReference
.collection("app").doc('usr').collection(_id).
.get()
.then((querySnapshot) {
querySnapshot.docs.forEach((result) {
return result.data();
});
})
}
What I want to get is all the documents from this collection, and not only the last. With the code above I get this when calling getData()
Instance of 'Future < dynamic>'
What I want to get:
[{name: Victor, age: 18}, {name: Tommy, age: 40}]
How can I reach it?
UPDATE
If I run the code below...:
await databaseReference
.collection("app").doc('usr').collection(_id)
.get()
.then((QuerySnapshot snapshot) {
snapshot.docs.forEach((f) => print(f.data()));
})
In the console it prints all the documents but separately (First prints one, after another):
I/flutter (16316): {name: Victor, age: 18}
I/flutter (16316): {name: Tommy, age: 40}
UPDATE 2
If I write what #Sahil Chadha and #kovalyovi suggest, and just print the list ... :
var items = List<dynamic>();
... my code....
snapshot.docs.forEach((f) => items.add(f.data()));
return items;
//returns exactly what I want
... It returns exactly what I want, but if I write return items and in the calling do var a = getData();, The A value is Instance of future. How can I have the result expected?
UPDATE 3
I forgot the await before getData(). Now it's working:
var a = await getData();
print(a); //my expected result
getData() async {
var items = List<dynamic>();
await databaseReference
.collection("app").doc('usr').collection(_id)
.get()
.then((QuerySnapshot snapshot) {
snapshot.docs.forEach((f) => items.add(f.data()));
})
return items
}
To be able to store the data you receive in a form of a list, you will need to initialize a list at the beginning of the method and then populate that list where you forEach the response. As you mentioned in the comments, I am posting an answer here for you:
getData() async {
// initialize your list here
var items = List<dynamic>();
await databaseReference.collection("app").doc('usr').collection(_id).get().then((QuerySnapshot snapshot) {
snapshot.docs.forEach(
// add data to your list
(f) => items.add(f.data()),
);
});
return items;
}
Like there is no problem with the code. It will be healthier if you just model the data you get. You just did not mention that you were async where you were calling. Since it is future data, there must be a waiting event.
Future<List<User>> getData() async {
await databaseReference
.collection("app").doc('usr').collection(_id).
.get()
.then((querySnapshot) {
querySnapshot.docs.map((f) =>
User.fromJson(f)).toList());
})
}
You should try two of them.
List<User> loaddata=await getData();
--OR------
var loadData;
getData().then((result){
List<User>=result;
});
and you create the user model. It will be more revealing if you do it this way.
import 'dart:convert';
User userFromJson(String str) => User.fromJson(json.decode(str));
String userToJson(User data) => json.encode(data.toJson());
class User {
User({
this.name,
this.age,
});
String name;
int age;
factory User.fromJson(Map<String, dynamic> json) => User(
name: json["name"],
age: json["age"],
);
Map<String, dynamic> toJson() => {
"name": name,
"age": age,
};
}
If there is a problem in the code, you are calling the getData method in the build widget. So you have to call it from initstate.
Related
In my app I have a model which consists of the store name and store image and looks like this:
class StoreModel
{
String? imageofStore;
String? storeName;
StoreModel({ this.imageofStore, this.storeName});
//data from server
factory StoreModel.fromMap(map)
{
return StoreModel(
imageofStore: map['imageofStore'],
storeName: map['storeName'],
);
}
// data to server
Map<String, dynamic> toMap(){
return{
'imageofStore': imageofStore,
'storeName': storeName,
};
}
}
and my database for stores looks like this:
to call the store name I use initstate and setState as such:
class addStore extends StatefulWidget {
const addStore({Key? key}) : super(key: key);
#override
_addStoreState createState() => _addStoreState();
}
class _addStoreState extends State<addStore> {
User ? user = FirebaseAuth.instance.currentUser;
StoreModel storebox = StoreModel();
#override
void initState()
{
super.initState();
FirebaseFirestore.instance
.collection("stores")
.doc("XQjbm665g2a2xAiiydjr")
.get()
.then((value){
this.storebox = StoreModel.fromMap(value.data());
setState(() {
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Text("${storebox.storeName}"),
);
}
}
With this, I get the store name of the store with id XQjbm665g2a2xAiiydjr displaying but the thing is I want to get the name of all the stores. I know I need to change the .doc() but im not sure as to what I am to put in it that will start displaying all the names. Can someone please help?
By providing a document id, what you're getting is a DocumentSnapshot which is the data of a particular Document, but when you remove it, you get QuerySnapshot which is a list of the data of all the documents. So, to read all, you change your code as:
List<StoreModel> storesList = [];
FirebaseFirestore.instance
.collection("stores")
.get()
.then((value){
//Now, this value is of type QuerySnapshot unlike earlier.
if (value != null && value.docs.isNotEmpty) {
//If it comes here means the collection is not empty.
//Because this value is a list of DocumentSnapshot, We've to map it to extract documents.
//After mapping, returning it as a list and assigning it to storesList
storesList = value.docs.map((doc) => StoreModel.fromMap(doc.data())).toList();
setState(() {
});
} else {
//If it comes here means there are no documents in your collection.
//Notify User there's no data.
}
});
This code will get all the documents your collection have, but, you can limit or filter using limit or where respectively, just place .limit(int) or .where() before .get().
for model try to convert to json or you can use your either way this is just for example model
import 'dart:convert';
List<StoreModel> storeModelFromJson(String str) => List<StoreModel>.from(json.decode(str).map((x) => StoreModel.fromJson(x)));
String storeModelToJson(List<StoreModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class StoreModel {
StoreModel({
this.imageofStore,
this.storeName,
});
final String? imageofStore;
final String? storeName;
factory StoreModel.fromJson(Map<String, dynamic> json) => StoreModel(
imageofStore: json["imageofStore"] == null ? null : json["imageofStore"]!,
storeName: json["storeName"] == null ? null : json["storeName"]!,
);
Map<String, dynamic> toJson() => {
"imageofStore": imageofStore == null ? null : imageofStore!,
"storeName": storeName == null ? null : storeName!,
};
}
try this get all list
static Future<List<StoreModel>> getStorelist() async {
List<dynamic> list = [];
await FirebaseFirestore.instance
.collection("stores")
.get()
.then((value){
for(var x in value.docs){
final Map<String,dynamic> toMap = x.data() as Map<String,dynamic>;
/// Try to print all data first to see if fetching use log to view on terminal
log(toMap.toString());
list.add(toMap);
}
});
return list.map((e)= > StoreModel.fromJson(e)).toList();
}
as in my case to get only 1 data
static getSingleName(String? uid) async {
final result =
await FirebaseFirestore.instance
.collection("stores").doc(uid).get();
final response = result.data() as Map<String, dynamic>;
return response['storeName'].toString();
}
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}
})
I am using Firebase as backend. Each user has some items and these items cannot be seen by other users. User items are stored in a sub-collection. This is how the structure looks like:
User collection -> User id as document id -> in each document, a sub-collection of items -> item as document.
The app needs to get user id from Firestore and then it can show items of the user.
#override
Stream<List<Item>> items() {
final currentUserId = userRepo.getUserUid();
return Firestore.instance.collection('users')
.document(currentUserId) //error here
.collection("items").snapshots().map((snapshot) {
return snapshot.documents
.map((doc) => Item.fromEntity(ItemEntity.fromSnapshot(doc)))
.toList();
});
}
Future<String> getUserUid() async {
return (await _firebaseAuth.currentUser()).uid;
}
currentUser throws the following error:
The argument type 'Future<String>' can't be assigned to the parameter type 'String'.
I understand that the parameter expects a String and I can't assign a Future but I don't know how use future with stream and resolve the issue. If I replace currentUserId variable with a String like "36o1avWh8cLAn" (the actual user id) it does work.
Any help would be appreciated.
Update:
thanks to Viren V Varasadiya the problem is solved.
#override
Stream<List<Item>> items() async*{
final currentUserId = userRepo.getUserUid();
yield* Firestore.instance.collection('users')
.document(currentUserId) //error here
.collection("items").snapshots().map((snapshot) {
return snapshot.documents
.map((doc) => Item.fromEntity(ItemEntity.fromSnapshot(doc)))
.toList();
});
}
You can use async* annotation to use await in function which return stream.
Stream<List<Item>> items() async*{
final currentUserId = await userRepo.getUserUid();
yield Firestore.instance.collection('users')
.document(currentUserId) //error here
.collection("items").snapshots().map((snapshot) {
return snapshot.documents
.map((doc) => Item.fromEntity(ItemEntity.fromSnapshot(doc)))
.toList();
});
}
I am working on a group chat app with Flutter and the Firestore Plugin. Getting the data from the database and converting the snapshot into a List of Messages works totally fine. But now I want to convert the uid from the database into the username (the uids and their usernames are saved in the db). This is my code:
final CollectionReference messagesCollection =
Firestore.instance.collection('messages');
final CollectionReference usersCollection =
Firestore.instance.collection('users');
Future<String> getUsernameByUID(String _uid) async {
String username =
await usersCollection.document(uid).get().then((querySnapshot) {
return (querySnapshot.data["username"]);
});
return username;
}
List<Message> _messagesFromSnapshot(QuerySnapshot snapshot){
return snapshot.documents.map((doc) {
String username = await getUsernameByUID(doc.data["uid"]);
return Message(
text: doc.data["text"] ?? "",
username: username ?? "",
time: doc.data["time"] ?? "",
);
}).toList();
}
Stream<List<Message>> get messages {
return messagesCollection
.orderBy("time")
.snapshots()
.map(_messagesFromSnapshot);
}
The problem is in this line, because I cannot run this async code inside of the map().
String username = await getUsernameByUID(doc.data["uid"]);
Is there a solution to fix this? Thanks in advance.
async functions must return a Future, so adding async keyword to your callback means that your List.map() call must now return a List of Futures.
You can convert a List<Future<Message>> to a List<Message> by using Future.wait:
Future<List<Message>> _messagesFromSnapshot(QuerySnapshot snapshot) async {
var futures = snapshot.documents.map((doc) async {
String username = await getUsernameByUID(doc.data["uid"]);
return Message(
text: doc.data["text"] ?? "",
username: username ?? "",
time: doc.data["time"] ?? "",
);
});
return await Future.wait(futures);
}
Of course, Future.wait returns a Future and must be awaited, so now _messagesFromSnapshot must be async as well. Asynchrony is contagious, and that then would affect any callers of _messagesFromSnapshot.
Since the messages getter returns a Stream and is already asynchronous, I believe that you instead can use Stream.asyncMap:
Stream<List<Message>> get messages {
return messagesCollection
.orderBy("time")
.snapshots()
.asyncMap(_messagesFromSnapshot);
}
What you need is Future.forEach method. it iterates over a list and waits until all the async methods to finish before moving on to the next item in the list.
Future<List<Message>> _messagesFromSnapshot(QuerySnapshot snapshot) async {
List<Message> _messages = [];
await Future.forEach(snapshot.documents, (doc) async {
String username = await getUsernameByUID(doc.data["uid"]);
_messages.add(
Message(
text: doc.data["text"] ?? "",
username: username ?? "",
time: doc.data["time"] ?? "",)
);
});
return _messages;
}
Here is an example dartpad
I'm implementing a Todo Application in Flutter. I need to merge a double query in client, in order to perform an OR request in Firestore.
One hand, I have the following code that performs the double queries.
Future<Stream> combineStreams() async {
Stream stream1 = todoCollection
.where("owners", arrayContains: userId)
.snapshots()
.map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
Stream stream2 = todoCollection
.where("contributors", arrayContains: userId)
.snapshots()
.map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
And other hand, I have the following code that will perform the update of view with the Bloc pattern.
Stream<TodosState> _mapLoadTodosToState(LoadTodos event) async* {
_todosSubscription?.cancel();
var res = await _todosRepository.todos(event.userId);
_todosSubscription = res.listen(
(todos) {
dispatch(
TodosUpdated(todos));
},
);
}
I have the following error.
flutter: Instance of '_AsBroadcastStream<List<List<Todo>>>'
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: type 'List<List<Todo>>' is not a subtype of type 'List<Todo>'
I tried to look for more infos with the debugger and it turned out that my StreamZip source contains the 2 stream separately.
For the moment I can get one stream at a time.
I don't know how to proceed in order to get the 2 streams and display them.
You're doing a map of a map, which returns a List of another List.
You should zip the QuerySnapshot streams and do the mapping after creating the subscription, and then you can create a new Stream<List<Todo>> from it.
///private method to zip QuerySnapshot streams
Stream<List<QuerySnapshot>> _combineStreams() async {
Stream stream1 = todoCollection
.where("owners", arrayContains: userId)
.snapshots()
});
Stream stream2 = todoCollection
.where("contributors", arrayContains: userId)
.snapshots()
});
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
///exposed method to be consumed by repository
Stream<List<Todo>> todosStream() {
var controller = StreamController<List<Todo>>();
_combineStreams().listen((snapshots) {
List<DocumentSnapshot> documents;
snapshots.forEach((snapshot) {
documents.addAll(snapshot.documents);
});
final todos = documents.map((document) {
return Todo.fromEntity(TodoEntity.fromSnapshot(doc));
}).toList();
controller.add(todos);
});
return controller.stream;
}