Flutter Return Length of Documents from Firebase - firebase

Im trying to return the length of a list of documents with this function:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
respectsQuery.getDocuments().then((data) {
var totalEquals = data.documents.length;
return totalEquals;
});
}
I'm initialize this in the void init state (with another function call:
void initState() {
totalLikes(postID).then((result) {
setState(() {
_totalRespects = result;
});
});
}
However, when this runs, it initially returns a null value since it doesn't have time to to fully complete. I have tried to out an "await" before the Firestore call within the Future function but get the compile error of "Await only futures."
Can anyone help me understand how I can wait for this function to fully return a non-null value before setting the state of "_totalRespsects"?
Thanks!

I think you're looking for this:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
var querySnapshot = await respectsQuery.getDocuments();
var totalEquals = querySnapshot.documents.length;
return totalEquals;
}
Note that this loads all documents, just to determine the number of documents, which is incredibly wasteful (especially as you get more documents). Consider keeping a document where you maintain the count as a field, so that you only have to read a single document to get the count. See aggregation queries and distributed counters in the Firestore documentation.

Perfect code for your problem:
int? total;
getLength() async {
var getDocuments = await DatabaseHelper.registerUserCollection
.where("register", isEqualTo: "yes")
.get();
setState(() {
total = getDocuments.docs.length;
});
}
#override
void initState() {
super.initState();
getLength();
if (kDebugMode) {
print(total);
}
}

Related

How to await inside a stream while querying data from Firebase firestore

For context I'm using Getx state management for flutter and i need to call list.bindStream(availabilityStream()) on my Rx<List<Availability>> object.
here is my availabilityStream method
static Stream<List<Availability>> availabilityStream() {
return FirebaseFirestore.instance
.collection('availability')
.where('language',
isEqualTo: GetStorageController.instance.language.value)
.snapshots()
.map((QuerySnapshot query) {
List<Availability> results = [];
for (var availablity in query.docs) {
availablity["cluster"].get().then((DocumentSnapshot document) {
if (document.exists) {
print("Just reached here!");
//! Ignore doc if cluster link is broken
final model = Availability.fromDocumentSnapshot(
availabilityData: availablity, clusterData: document);
results.add(model);
}
});
}
print("result returned");
return results;
});
}
the cluster field on my availability collection is a reference field to another collection. The problem here is i need to await the .get() call to my firestore or the function returns before the data gets returned. I can't await inside the map function or the return type of Stream<List> changes. so how can i await my function call here?
using the advice i got from the comments I've used Stream.asyncMap to wait for all my network call futures to complete.
Here is my updated Repository
class AvailabilityRepository {
static Future<Availability> getAvailabilityAndCluster(
QueryDocumentSnapshot availability) async {
return await availability["cluster"]
.get()
.then((DocumentSnapshot document) {
if (document.exists) {
//! Ignore doc if cluster link is broken
final model = Availability.fromDocumentSnapshot(
availabilityData: availability, clusterData: document);
return model;
}
});
}
static Stream<List<Availability>> availabilityStream() {
return FirebaseFirestore.instance
.collection('availability')
.where('language',
isEqualTo: GetStorageController.instance.language.value)
.snapshots()
.asyncMap((snapshot) => Future.wait(
snapshot.docs.map((e) => getAvailabilityAndCluster(e))));
}
}
How i think this works is that the normal .map function returns multiple promises form the getAvailabilityAndCluster() method then all of the processes that execute asynchronously are all put to Future.wait() which is one big promise that waits all the promises inside it to complete. Then this is passed onto .asyncMap() which waits for the Future.wait() to complete before continuing with its result.

Flutter Firebase async query not retrieving data inside a stream function

I am trying to query a User from firebase within another query but for some reason but I can't get the code to work
The function the wont run is await usersRef.doc(uid).get(); and can be found here:
static getUserData(String uid) async {
return await usersRef.doc(uid).get();
}
static DirectMessageListModel getDocData(QueryDocumentSnapshot qdoc, String uid) {
Userdata postUser = Userdata.fromDoc(getUserData(uid));
return DirectMessageListModel.fromDoc(qdoc, postUser);
}
static DirectMessageListModel fromDoc(QueryDocumentSnapshot doc, Userdata altUser) {
return DirectMessageListModel(
doc['chatId'],
doc['lastMsgContent'],
doc['lastMsgType'],
altUser
);
}
parent function:
Stream<List<DirectMessageListModel>> getMeassageList(){
var snaps = FirebaseFirestore.instance.collection('directMessages').where('users', arrayContains: userdata!.uid).snapshots();
List<String> usersListElement = [];
return snaps.map((event) { return event.docs.map((e) {
usersListElement = [e.get('users')[0], e.get('users')[1]];
usersListElement.remove(userdata!.uid);
return DirectMessageListModel.getDocData(e, usersListElement.first);
}).toList();
});
}
You forgot to wait for the future getUserData(uid) to complete.
Try this:
static Future<DocumentSnapshot<Object>> getUserData(String uid) async {
return await usersRef.doc(uid).get();
}
static DirectMessageListModel getDocData(
QueryDocumentSnapshot qdoc,
String uid,
) async {
Userdata postUser = Userdata.fromDoc(await getUserData(uid)); // await here
return DirectMessageListModel.fromDoc(qdoc, postUser);
}
..
// parent function.
// Also wait for the future in the parent function.
// UPDATE BELOW! Define the parent function like this:
Stream<List<Future<DirectMessageListModel>>> getMeassageList() {
var snaps = FirebaseFirestore.instance
.collection('directMessages')
.where('users', arrayContains: userdata!.uid)
.snapshots();
List<String> usersListElement = [];
return snaps.map((event) {
return event.docs.map((e) async {
usersListElement = [e.get('users')[0], e.get('users')[1]];
usersListElement.remove(userdata!.uid);
return await DirectMessageListModel.getDocData(e, usersListElement.first);
}).toList();
});
}
NB: You are fetching user data (either sender/receiver) for each message in directMessages collection. It might be better to store just sender/receiver name in directMessages collection and simply display that. Then if the user clicks on a message, you can then fetch the full sender/receiver data.

Admin access with flutter - hide and show widget and button based on User right firebase

I am working to build an admin access to a client. Among the visibility I need to constraint is the visibility of the button.
When changing access to user to admin, the button is not appearing back. The dependent boolean condition is mentioned below.
bool _show = false;
void showFloationButton() {
setState(() {
_show = true;
});
}
void hideFloationButton() {
setState(() {
_show = false;
});
}
void userAdminAccess() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
if ( currentUser != null) {
Firestore.instance
.collection('Users')
.where('isAdmin', isEqualTo: true);
} return showFloationButton();
}
Your code looks like it wants to perform a query, but it is not actually doing so. It's just building a Query object. You have to use get() to actually make that query happen, and await the response:
var querySnapshot = await Firestore.instance
.collection('Users')
.where('isAdmin', isEqualTo: true)
.get();
if (querySnapshot.size > 0) {
// there is at least one document returned by this query
}
else {
// there are not matching documents
}
I suggest learning more about how to perform queries in the documentation.
Note that what you're doing is potentially very expensive. It seems to me that you should probably get a single document for the user, identified by their UID, and look for a field within that document. Getting all admins could incur a lot of reads unnecessarily.

Flutter: How to Notify main widget after inserting all data into database

In my StatefulWidget in initState i have a method:
#override
void initState() {
super.initState();
getMyChannels();
}
getMyChannels method run a http method to get data from service and store data into database:
void getMyChannels() async {
// get data from servise and store them
_myChannel = await MyToolsProvider()
.getChannelMe("df6b88b6-f47d****");
getToolsRelToChannels(); // get data from database
setState(() {});
}
As you can see i have getToolsRelToChannels method. This method fetch data from local database. This data must be stored by await MyToolsProvider()
.getChannelMe("df6b88b6-f47d****"); method into database.
This is .getChannelMe method:
Future<ProgramsByActiveToolsModel> getChannelMe(String auth) async {
Map<String, dynamic> header = {
'Content-Type': "application/json",
"Authorization": 'Bearer $auth'
};
try {
var result = await NetworkCLient()
.getRequest(url: '$URL/api/me', header: header);
if (result != null) {
var programsByActiveToolsModel =
ProgramsByActiveToolsModel.fromJson(result);
if (programsByActiveToolsModel.responseCode == 200) {
programsByActiveToolsModel.data.forEach((item) async {
await DBProvider.db.addMyTools(item);
saveToolsbyChannelId(header, item.id);
});
return programsByActiveToolsModel;
} else
return null;
}
} catch (e) {
throw e;
}
}
In addMyTools method i store each data in one table of my database and i call saveToolsbyChannelId method for each item. This is main data that I need too.
Future<void> saveToolsbyChannelId(Map header, int channelId) async {
header["Authorization"] = 'Bearer 92122926-****';
try {
var count = await DBProvider.db.getCountToolsbyChannelId(channelId);
if (count == 0) {
var result = await NetworkCLient().getRequest(
url: '$URL/api/channel/$channelId', header: header);
if (result != null) {
var logToolsRunModel = LogTools.LogToolsRunModel.fromJson(result);
if (logToolsRunModel.responseCode == 200) {
logToolsRunModel.data.forEach((item) {
DBProvider.db.addTools(item);
});
}
}
}
} catch (e) {
throw e;
}
}
After fetching data from my service i sore these data into sqlite database .Now it's await MyToolsProvider().getChannelMe job is done!
It's time to explain getToolsRelToChannels();:
void getToolsRelToChannels() async {
_toolsRun =
await MyToolsProvider().getToolsRelatedMyChannel(_selectedChannel);
setState(() {});
}
getToolsRelatedMyChannel this method must be wait till all data in this method DBProvider.db.addTools(item) added into database and after inserting my widget must be recreated.
Future<List<ToolsByChannelIdDbModel>> getToolsRelatedMyChannel(
int channelId) async {
List<ToolsByChannelIdDbModel> list = List<ToolsByChannelIdDbModel>();
try {
var result = await DBProvider.db.getToolsById(channelId);
result.forEach((item) {
list.add(ToolsByChannelIdDbModel.fromJson(item));
});
return list;
} catch (e) {
print(e);
}
}
}
but my code is wrong because after running await MyToolsProvider().getChannelMe(***) getToolsRelToChannels method is executed and nothing is stored into database to fetching yet!!!
How could i notify my main widget after finishing database inserting???
I can not to use FutureBuilder because when run for first time, my database is empty !!!
You should await saveToolsbyChannelId in getChannelMe and await DBProvider.db.addTools(item); in saveToolsbyChannelId, otherwise you are trying to read from the database before the data has been written to it. This is assuming the rest of your code is correct, which we cannot tell for sure because there are lots of variables such as _selectedChannel that we know nothing about.
UPDATED - Check below.
What you want is to await ALL async operations. In your case
#override
void initState() async {
super.initState();
await getMyChannels();
}
and
await saveToolsbyChannelId(header, item.id);
and if DBProvider.db.addTools is asynchronous, then
await DBProvider.db.addTools(item);
UPDATE:
Since its not possible to make initState() async, you can use a callback in the future:
#override
void initState() {
super.initState();
var channelsFuture = getMyChannels();
channelsFuture.then((resp){
setState(() {});
});
}
I'd suggest that you reconsider your whole approach (from a clean architecture point of view). Take a look at the BLoC pattern. It basically boils down to this :
There's another class (called the BLoC) which handles the business logic (getting data from network and adding to the database). The widget class only handles the UI.
You kick off your asynchronous processing in the BLoC class from the widget's initState(). There's a stream listener in the widget that listens to the completion of the task by BLoC.
As soon as the BLoC completes the async task, it notifies the widget by using a stream. The stream listener in the widget knows that the data is ready and it updates the UI by calling setState();
Done !!

Flutter wait for Firestore to complete

I am using flutter with firebase to create an app. The function below gets some data from my firebase database, but the problem is that it takes too long to get the data. How can make sure the app waits for the firebase getting data to finish first before proceeding?
I would like to do something like an await, but I don't know if I can do:
await Firestore.instance...
Code:
void getData() {
Firestore.instance
.collection('collection')
.document('document')
.get()
.then((DocumentSnapshot ds) {
var count = ds.data.length;
for(var i = 0; i < count; i ++){
Firestore.instance
.collection('collection')
.document('document')
.get()
.then((DocumentSnapshot dss) {
// do something
});
}
});
}
To use await, you have to make your function async :
Future<void> getData() async {
Then yes, you can do
var result = await Firestore.instance.collection...
instead of handling the Future result in the then() callback. The next line will not be executed until the Future is resolved.
You will need to await for the firestore to return data.
and then return it.
Future getData() async {
DocumentSnapshot ds = await Firestore.instance
.collection('collection')
.document('document')
.get();
final data = ds.//do something with document snapshot
return data;
}
Hope this helps.

Resources