How to get Firestore Data by method in Flutter - firebase

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.

Related

Firebase getting all docs from a collection

Hello I want to get all docs from a collection in one shot without knowing the docs id's since they are random. Inside each doc I have some data but all I need is the doc itself than I will take the data from each and every one no problem.
I get null every time.
Does anyone know what am I doing wrong?
Thank you in advance.
This is the code :
import 'package:cloud_firestore/cloud_firestore.dart';
Future<Map<String, dynamic>> getVisitedCountries(String ID) async {
Map<String, dynamic> val = <String, dynamic>{};
await FirebaseFirestore.instance
.collection('users')
.doc(ID)
.collection('PersonalData')
.doc(ID)
.collection('Passport')
.doc(ID)
.collection('VisitedCountries')
.doc()
.get()
.then((value) {
if (value.data().isEmpty) {
print("User not found");
} else {
val = value.data();
}
}).catchError((e) {
print(e);
});
return val;
}
This is the structure in the Cloud Firestore
So for everyone who is having this problem, this is the way to solve it.
I solved it thanks to the user : Kantine
Solution : code :
import 'package:cloud_firestore/cloud_firestore.dart';
Future<Iterable> getVisitedCountries(String ID) async {
// Get data from docs and convert map to List
QuerySnapshot querySnapshot = await FirebaseFirestore.instance
.collection('users')
.doc(ID)
.collection('PersonalData')
.doc(ID)
.collection('Passport')
.doc(ID)
.collection('VisitedCountries')
.get();
final val = querySnapshot.docs.map((doc) => doc.data());
return val;
}
I used a query snapshot to get the data and then mapped it.

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

Querying Firestore using 2 where clause in flutter

I have two attributes in my eventClick table eventID and userID so what I am trying to do is if eventID and userID normally exists then show You have already clicked if not then make an entry of the click. I thought of using two clause and merging it but that just resulted in insertion of the values.
I am trying using the below code
final checkSnapshot = FirebaseFirestore.instance
.collection('eventClick')
.where('eventID', isEqualTo: eventID)
.where('userID', isEqualTo: userID)
.snapshots();
I want the working to be like
if (checkSnapshot.exists) {
print('already exists');
} else {
FirebaseFirestore.instance.collection('eventClick').add({
'eventID': eventID,
'eventName': eventName,
'eventImageUrl': eventImageUrl,
'userID': userID
});
}
Can you try using .get() ? That should return a QuerySnapshot which has a 'size' property. If 0, that means no such document exists.
FirebaseFirestore.instance
.collection('eventClick')
.where('eventID', isEqualTo: eventID)
.where('userID', isEqualTo: userID)
.get()
.then((checkSnapshot) {
print(checkSnapshot.docs[0]);
if (checkSnapshot.size > 0) {
print("Already Exists");
} else {
//add the document
}
});
FirebaseFirestore.instance
.collection('task')
.where('usergroup', isEqualTo: 'software')
.where('userid', isEqualTo: 'ep434334')
.snapshots(),

Flutter - Receive and then modify data from Stream

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

How to build a Stream based on a Future result in Flutter?

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

Resources