How to combine to different firebase query streams into one stream? - firebase

I've reached a roadblock while trying to create a chat app. I want to merge two different streams to one, to create my Chat Screen. For example-
If user1 sends a message to user2, it gets stored in-
FirebaseFirestore.instance.collection("users").doc(user1).collection("data").doc(user2).collection("chats")
Similarly, if user2 sends a message to user1, it gets stored in-
FirebaseFirestore.instance.collection("users").doc(user2).collection("data").doc(user1).collection("chats")
All the chats sent by a user are stored in the chats collection, inside the a doc named the receivers uid.
So essentially, I need both the data insede-FirebaseFirestore.instance.collection("users").doc(user1).collection("data").doc(user2).collection("chats") (to get the chats sent by user1 to user 2) and FirebaseFirestore.instance.collection("users").doc(user1).collection("data").doc(user2).collection("chats") (to get chats sent by user2 to user1) to create the chat screen.
I want to know how to merge these two streams together into one, so I can use the data to create the chat widgets. So far this is what i've got-
Note: Continuing the analogy, user1's uid is FirebaseAuth.instance.currentUser!.uid, and user2's uid is stored in widget.uid.
StreamBuilder(
//To get messaages sent by the current user to user2 or widgets.uid
stream: FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("data")
.doc(widget.uid)
.collection("chats")
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>>
UserMessagesSnapshots) {
if (UserMessagesSnapshots.connectionState ==
ConnectionState.waiting) {
return const LoadingScreen();
} else {
return StreamBuilder(
//to get messages sent by user2 to user1
stream: FirebaseFirestore.instance
.collection("users")
.doc(widget.uid)
.collection("data")
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("chats")
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>>
FriendMessagesSnapshot) {
if (FriendMessagesSnapshot.connectionState ==
ConnectionState.waiting) {
return const LoadingScreen();
} else {
//and here i have both the streams.
}
});
}
},
),

Thanks #mariofrancios!
I created this function to merge the streams to one-
Stream<List<QuerySnapshot>> getMessages() {
Stream userMessages = FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("data")
.doc(widget.uid)
.collection("chats")
.snapshots();
Stream friendMessages = FirebaseFirestore.instance
.collection("users")
.doc(widget.uid)
.collection("data")
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("chats")
.snapshots();
return StreamZip([(userMessages as dynamic), (friendMessages as dynamic)]);
}
And listened to it like this-
...StreamBuilder(
stream: getMessages(),
builder: (context, AsyncSnapshot<List<QuerySnapshot>> snap) {
if (snap.connectionState == ConnectionState.waiting) {
return const LoadingScreen();
} else {
// access the data with snap.data![n].docs[n].data()
}
},
),

Related

Stream provider is not filtering data according to where clause that I specified,

I have a stream provider that should return userData of the current logged in user. It should take information of user whose email is equal to current user.email.I guess the stream provider load data before the email of the current user is saved b'se it load information of all users `. So how should I make stream provider to wait until the email of the current user is saved .
The function that load current user.
Future<void>loadLoggedUser() async{
FirebaseAuth.instance
.userChanges()
.listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
email=user.email;
userid=user.uid;
print('User is signed in!');
}
});
notifyListeners();
}
Stream provider for userData
Stream<List<UserData>> get UserList {
return _db.collection('users').where('email', isEqualTo: email)
.snapshots()
.map((snapshot) =>
snapshot.docs
.map((document) =>
UserData.fromJson(document.data())).toList()
);
}
Any help will be appreciated
You can also search like this if you want -
Nested Streambuilder can help to achieve this sorting and searching problem.
1st StreamBuilder is of User and second for QuerySnapshot<Map<String, dynamic>>
StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.hasData) {
final String email = snapshot.data!.email;
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.where('email', isEqualTo: email)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>>
snapshot) {
if (snapshot.hasData) {
final list = snapshot.data!.docs
.map((document) => UserData.fromJson(document.data()))
.toList();
if (list.isNotEmpty) {
return ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
final userData = list[index];
return Text(userData
.name); // can print properties of userData model
},
);
}
return const Text("NO DATA AVAILABLE");
}
return const Text("Loading");
},
);
}
return const Center(
child: Text("logged out"),
);
},
),
I would suggest turning getter into a function and checking if user email is null or empty string (depending on how you initialise it in your code).
Stream<List<UserData>> getUserList(String? email) {
if (email == null) throw Exception("Email is null");
return _db
.collection('users')
.where('email', isEqualTo: email)
.snapshots()
.map((snapshot) => snapshot.docs
.map((document) => UserData.fromJson(document.data()))
.toList());
}

How do I get data from multiple documents with StreamBuilder?

I am trying to make my Flutter app with chat.
I use a streambuilder to update the chat data.
My problem is i don't know how i can read multiple documents with streambuilder.
Here my database:
My plan i would like get all data from this documents.
Before, i storage the id in a object and i use a for loop to get all data from documents.
The documents can be a random count (2 or 10...).
Here my streambuilder:
body() {
//build stream link get id
Stream _build_stream_id() async* {
//load user
var user_id = await StorageUserID.loadData();
yield* FirebaseFirestore.instance
.collection('chat')
.doc('users')
.collection(user_id)
.snapshots();
}
//build stream link get data
Stream _build_stream_data(chat_overview_object, index) async* {
yield* FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc(chat_overview_object[index].chat_overview_id[0])
.snapshots();
}
return StreamBuilder(
stream: _build_stream_id(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var chat_overview_object = query_chat_overview_data_1(snapshot.data);
for (var i = 0; i < chat_overview_object.length; i++) {
return StreamBuilder(
stream: _build_stream_data(chat_overview_object, i),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('test: ' + snapshot.data.toString());
} else {
return Text("No data");
}
},
);
}
} else {
return Text("No data");
}
return Text('data');
},
);
}
If you find a better way pls tell me.
If you have questions, feel free to ask me.
Many thx (:
You can combine streams using the StreamGroup from the async package.
import 'package:async/async.dart' show StreamGroup;
List<Stream> streamList = [stream1, stream2];
Stream combinedStream = StreamGroup.merge(streamList);
Though handling Firestore sollections with different fields might be tricky.

Download Data from firebase in flutter

I want to build a contactScreen for my flutter app. Therefor I have to download an array from Firebase. I am just able to download directly into a listView in flutter and get stuck while coding. Heres my code:
var currentUser = FirebaseAuth.instance.currentUser!.uid;
var contacts;
getUserData() async {
var userData = await FirebaseFirestore.instance
.collection('users')
.where('uid', isEqualTo: currentUser)
.get();
contacts = userData['contacs']; //heres the error
}
At first I want to initialize the currentUser's UID and then get the currentUser's contacts array from firebase. Therefor I build the getUserData() method to download the User and then initialize his contacts array.
The last step doesn't work in Flutter, I can't access the contacts array. Is the way I want to get the data correct?
You're at the very least missing an await before the get() call:
var userData = await FirebaseFirestore.instance
.collection('users')
.where('uid', isEqualTo: FirebaseAuth.instance.currentUser!.uid)
.get();
Without await your userData is of type Future<QuerySnapshot<Map<String, dynamic>>> as you get in the error message. By using await, its type will become QuerySnapshot<Map<String, dynamic>>.
you need to call await or use FutureBuilder
like this
FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.where('uid', isEqualTo: FirebaseAuth.instance.currentUser!.uid)
.get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: [Text(snapshot.data['name'])], //error here
);
}
return Loading();
},
)

Error getting data from subcollection of flutter firebase

In a flutter messaging app, chatrooms are created and on the conversation screen, I can access the subcollection of the messages. But when the same subcollection I am trying to access on the main page
(where existing chats are shown) I cannot access them.
I have a collection of ChatRooms, in which users as an array are stored. Then, Messages named subcollection stores the messages.
See, the document is named lexicographically attached with and in between. Further, it has Messages collection.
And messages collection is also not empty.
On the main page, the existing chats are shown in listTile. I want to show the last message in its subtitle.
So, here is the last message stateful widget.
class _LastMessageState extends State<LastMessage> {
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection("ChatRooms")
.doc(widget.chatRoomID)
.collection("Messages")
.orderBy("Time")
.snapshots()
.last,
builder: (context, documentSnapshot) {
return Text(documentSnapshot.data.docs.last.get("Message"));
});
}
}
Always the bad state error is coming up.
I would be glad if you could figure out the problem.
Edit :
This is my firestore rules.
You should use the limit() method, in order to get a QuerySnapshot with only one document. Then you can do as follows, knowing the first (and unique) element of the docs list is the document you are looking for:
return FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance.
.collection("ChatRooms")
.doc(widget.chatRoomID)
.collection("Messages")
.orderBy("Time", descending: true)
.limit(1)
.get(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text("...");
}
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.size > 0) {
return Text(snapshot.data.docs[0].get("Message"));
} else {
return Text("No document");
}
}
return Text("Loading");
},
);

How can I filter a stream with document ids?

How can I filter the documents in the groups/collection with the document IDs in user/uid/usergroups ?
I want to filter the groups/ with the document IDs that are stored in user/uid/usergroups to show the user only the Groups
which are stored in his userprofile.
I don't get an error message with this Code but i don't see any Groups....
Stream<QuerySnapshot> get userGroupIDs {
return userGroupCollection.document(uid).collection('usergroups').snapshots();
}
Stream<QuerySnapshot> get group {
return db.collection('groups').where('GroupID', isEqualTo: userGroupIDs).snapshots();
}
.
StreamBuilder<QuerySnapshot>(
stream: group,
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return new Text('Connecting...',style: TextStyle(fontSize: 200),);
} else {return ListView.builder(
Try the following:
Stream<QuerySnapshot> userGroupIDs() async*{
String docId;
Stream<QuerySnapshot> snap = Firestore.instance.collection("users").document("uid").collection('usergroups').snapshots();
await for(var snapData in snap){
snapData.documents.forEach((docResult){
docId = docResult.documentID;
});
}
yield* Firestore.instance.collection('groups').where('GroupID', isEqualTo: docId).snapshots();
}
Then inside StreamBuilder:
StreamBuilder<QuerySnapshot>(
stream: userGroupIDs(),
First get the documentID from the collection usergroups, then use that id in the query with GroupID

Resources