Stream and Future in flutter - firebase

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();
});
}

Related

How to display all the names in database firestorefirebase flutter

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();
}

Flutter Firebase StreamBuilder List<Future<Item>> isn't <List<Item>> error

Hello I am quite experienced with Futures but Streams are new to me. I am having this error in my Stream builder function where I can't get the return type to be not a future
Code with error:
Stream<List<MessageModel>> getMessages() {
Stream<QuerySnapshot> snap = FirebaseFirestore.instance
.collection('messages')
.doc(groupChatId)
.snapshots();
return snap.map((event) => event.docs.map((doc) => return MessageModel.getMessagedata(doc)).toList());
}
other related functions:
class MessageModel {
static Future<MessageModel> getMessagedata(QueryDocumentSnapshot doc) async {
Userdata postUser = await DatabaseService.getPostUserDataFromFirestore(doc['userId']);
return MessageModel.fromDoc(doc, postUser);
}
factory MessageModel.fromDoc(QueryDocumentSnapshot doc, Userdata authordata) {
return MessageModel(
doc['content'],
doc['timestamp'],
doc['authorId'],
doc['type'],
authordata
);
}
}
error message:
The return type 'List<Future<MessageModel>>' isn't a 'List<MessageModel>', as required by the closure's context.
I also tried this code but got the same error:
return snap.map((event) => event.docs.map((doc) async => await return MessageModel.getMessagedata(doc)).toList());

I can't get data from firestore "Instance of 'Future<dynamic>'"

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.

Flutter + firestore : filter list

I have a function that gets documents from firestore.
List<Post> posts;
List<Post> filteredposts;
void getDeals() async {
QuerySnapshot snapshot = await Firestore.instance
.collection('deals')
.document('${widget.stad.toLowerCase()}')
.collection('deals')
.orderBy('endTime', descending: false)
.getDocuments();
List<Post> posts =
snapshot.documents.map((doc) => Post.fromDocument(doc)).toList();
setState(() {
this.posts = posts;
});
}
I would like to filter that list e.x. where the bool: (in the firestore document) "typeX" is equalto true.
setState(() {
filteredposts = posts.where((snapshot) => snapshot.typeX == true);
});
But I get the error message:
'WhereIterable<Post>' is not a subtype of type 'List<Post>'
What am I doing wrong?
Thank you very much!
Many of the List/Iterable helper methods like map, where, expand... etc return a variant of Iterable but not List as the underlying data structure need not be a List (e.g. it can be a Set).
So most of the times, you need to call toSet or toList on the result of these methods.
In your case it would be
filteredposts = posts.where((snapshot) => snapshot.typeX == true).toList();

Merge Firestore's separate queries Streams in Dart

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;
}

Resources