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();
},
)
Related
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()
}
},
),
I am trying to get users name but Flutter gives this error:
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.
Method:
String getUserNameFromUID(String uid) {
FirebaseFirestore.instance
.collection('users')
.where('uid', isEqualTo: uid)
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
return doc["name"];
});
});
}
How can I solve my problem? if I add return 0 to end of the method it always gives 0.
It always gives 0.(I do not want 0, I want get user name from uid)
String getUserNameFromUID(String uid) {
FirebaseFirestore.instance
.collection('users')
.where('uid', isEqualTo: uid)
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
return doc["name"];
});
});
return "0";
}
EDIT: I need a String solution, not Future. The method should return String...
Because my UI is not future builder. Isn't there any way to return one data as String in Firestore database?
First your function should return a Future<String> since it relies on firestore's get wich also returns a future. Also docs is a list, you have to return just one. The first one i guess. In the UI just use a FutureBuilder
Future<String> getUserNameFromUID(String uid) async {
final snapshot = await FirebaseFirestore.instance
.collection('users')
.where('uid', isEqualTo: uid)
.get();
return snapshot.docs.first['name'];
}
Since you can't use FutureBuilder. An ugly alternative is to pass a callback to getUserNameFromUID and call setState from there.
void getUserNameFromUID(String uid, Function (String name) onData) {
final snapshot = await FirebaseFirestore.instance
.collection('users')
.where('uid', isEqualTo: uid)
.get().then((s) => onData(s.docs.first['name']));
}
On your UI
...
getUserNameFromUID(uid, (String name){
setState(()=> name = name);
});
From your last comment just inherit from StatefulWidget. And call the function from inside.
#override
void initState() {
getUserNameFromUID(uid);
}
If you had special requirements about not being able to modify the UI, you should mention that as it conditions the way to use the backend services.
I followed some tutos and I add user on firestore successly but I can't print this user on the profile page when the current user is logging because I'm having a error too in my front end.
This is my entire frontend with this error:
The method '[]' can't be unconditionally invoked because the receiver can be 'null'. Try making the call conditional (using '?.') or adding a null check to the target ('!')."
I tried ? and ! but with not success.
hope you help me !
FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.doc('uid')
.get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text(snapshot.data['displayName'],
);
} else {
return Text("Loading...");
and my backend if needed
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
Future userSetup(String displayName) async {
CollectionReference users = FirebaseFirestore.instance.collection('Users');
FirebaseAuth auth = FirebaseAuth.instance;
String uid = auth.currentUser!.uid.toString();
await users.doc(uid).set({'displayName': displayName, 'uid': uid });
final result = await users.doc(uid).get();
final data = result.data() as Map<String, dynamic>;
return data['displayName'];
}
I'm attempting to do the following:
Listen to a Firestore stream so when a new document is added, the StreamBuilder will receive it, modify it, and then present it.
The "modification" takes the Stream data, which includes a Firestore UID, gets the data from Firestore with that UID, and then the StreamBuilder is populated with that data.
So the flow is: New document added -> Stream gets document -> Function gets UID from that document -> Function uses that UID to get more data from Firestore -> Function returns to populate StreamBuilder with that new data.
My current set-up is as follows -- which works, but the FutureBuilder is obviously making the Firestore call each time the widget is rebuilt, and nobody wants that.
Stream<QuerySnapshot> upperStream;
void initState() {
super.initState();
upperStream = aStream();
}
Stream<QuerySnapshot> aStream() {
return Firestore.instance
.collection('FirstLevel')
.document(/*ownUID (not related to stream)*/)
.collection('SecondLevel')
.snapshots();
}
Future<List> processStream(List streamData) async {
List futureData = List();
for (var doc in streamData) {
Map<String, dynamic> dataToReturn = Map<String, dynamic>();
DocumentSnapshot userDoc = await Firestore.instance
.collection('FirstLevel')
.document(/*OTHER USER'S UID FROM STREAM*/)
.get();
dataToReturn['i'] = userDoc['i'];
futureData.add(dataToReturn);
}
return futureData;
}
...
...
//The actual widget
Expanded(
child: StreamBuilder(
stream: upperStream,
builder: (context, snapshot) {
// Error/null handling
return FutureBuilder(
future: processStream(snapshot.data.documents),
builder: (context, futureSnap) {
// Error/null handling
return ListView.builder(
shrinkWrap: true,
itemCount: futureSnap.data.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
//Continuing with populating
});
});
}),
),
What's the best way to handle a flow like this? Creating a method where the data from the Firestore stream is modified and then returned without needing ListView.builder at all?
Edit: I tried creating my own stream like this:
Stream<Map<String, dynamic>> aStream2() async* {
QuerySnapshot snap = await Firestore.instance
.collection(FirstLevel)
.document(/*OWN UID*/)
.collection(SecondLevel)
.getDocuments();
for (var doc in snap.documents) {
Map<String, dynamic> data = Map<String, dynamic>();
DocumentSnapshot userDoc = await Firestore.instance
.collection(FirstLevel)
.document(/*OTHER USER'S UID RECEIVED FROM STREAM*/)
.get();
data['i'] = userDoc['i'];
yield data;
}
}
However, the Stream is not triggered/updated when a new Document is added to the SecondLevel collection.
Alright I think I found the path to the solution. I get the data from the stream, modify it, and then yield it to the StreamBuilder within one method and no longer need the FutureBuilder. The key to this, as Christopher Moore mentioned in the comment, is await for. The stream method looks like this:
Stream<List> aStream() async* {
List dataToReturn = List();
Stream<QuerySnapshot> stream = Firestore.instance
.collection(LevelOne)
.document(OWN UID)
.collection(LevelTwo)
.snapshots();
await for (QuerySnapshot q in stream){
for (var doc in q.documents) {
Map<String, dynamic> dataMap= Map<String, dynamic>();
DocumentSnapshot userDoc = await Firestore.instance
.collection('UserData')
.document(doc['other user data var'])
.get();
dataMap['i'] = userDoc['i'];
//...//
dataToReturn.add(dataMap);
}
yield dataToReturn;
}
}
And then the StreamBuilder is populated with the modified data as I desired.
I found myself using this to implement a chat system using the Dash Chat package in my app. I think using the map function on a stream may be a little cleaner here is a sample:
Stream<List<ChatMessage>> getMessagesForConnection(
String connectionId) {
return _db
.collection('connections')
.doc(connectionId)
.collection('messages')
.snapshots()
.map<List<ChatMessage>>((event) {
List<ChatMessage> messages = [];
for (var doc in event.docs) {
try {
messages.add(ChatMessage.fromJson(doc.data()));
} catch (e, stacktrace) {
// do something with the error
}
}
return messages;
});}
I have a Flutter app which uses Firebase-storage and google-signin.
the steps I am trying to do is so simple:
1- Sign-in using Google (Done).
2- Get Current User Id (Done).
3- Use the User Id when construct the stream for the stream builder (the problem).
what I did so far is that I am using a Future to get the Current User Id,
then to inject the user Id inside the Where clause
.where('userId', isEqualTo: userId)
and this is what I end up with:
this is the part where I should create the stream:
// Get document's snapshots and return it as stream.
Future<Stream> getDataStreamSnapshots() async {
// Get current user.
final User user = await FirebaseAuth().currentUser();
String userId = user.uid;
Stream<QuerySnapshot> snapshots =
db
.collection(db)
.where("uid", isEqualTo: userId)
.snapshots();
try {
return snapshots;
} catch(e) {
print(e);
return null;
}
}
and this is the part where should I call and receive the stream,
...
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: CALLING THE PREVIOUS FUNCTION,
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
...
}
...
But this code does not work, because I am not able to get the value that should returned by the Future? any idea?
thanks a lot
You should never have a Future<Stream>, that's double-asynchrony, which is unnecessary. Just return a Stream, and then you don't have to emit any events until you are ready to.
It's not clear what the try/catch is guarding because a return of a non-Future cannot throw. If you return a stream, just emit any error on the stream as well.
You can rewrite the code as:
Stream<QuerySnapshot> getDataStreamSnapshots() async* {
// Get current user.
final User user = await FirebaseAuth().currentUser();
String userId = user.uid;
yield* db
.collection(db)
.where("uid", isEqualTo: userId)
.snapshots();
}
An async* function is asynchronous, so you can use await. It returns a Stream, and you emit events on the stream using yield event; or yield* streamOfEvents;.