Not Loading the value in Real Time - firebase

I am using below code to get the total number of documents from the firestore collection and then store it to int countDocument, it does get the value accurately, but when I used to display the same value in the Flutter widget Text Widget which is nested in Scaffold, upon loading the screen it does not show the value, showing null only, only upon hot reload it shows the value on the screen.
To represent the value of countDocument in Text Widget, I did countDocument.toString()' but still it does not show the value upon initial loading of the screen
How should I resolve it?
void countDocuments() async {
StreamSubscription<QuerySnapshot> _myDoc = await Firestore.instance.collection('users').snapshots().listen((result) {
countDocument = result.documents.length;
print(countDocument);
});

You need to use a StatefulWidget, then using setState, you can change the data of the Text:
void countDocuments() async {
StreamSubscription<QuerySnapshot> _myDoc = await Firestore.instance
.collection('users')
.snapshots()
.listen((result) {
setState(() {
countDocument = result.documents.length;
});
print(countDocument);
});
}
setState will call the build method again with the new data. Inside the build method you can do the following:
Text(countDocument ?? "loading")

Related

Download data before build - method

I want to build a contactScreen for my flutter app with an array downloaded from firebase. The array is correctly downloaded, but while building the app the array stays empty. With some tests (the print-statements) I figured out, that the app builds the screen and at the same time download the data (with getUserData()). So the download is not fast enough. (After a reload everything works fine).
#override
void initState() {
super.initState();
getUserData();
print(contacts);
}
getUserData() async {
var userData = await FirebaseFirestore.instance
.collection('users')
.doc(currentUser)
.get();
print(userData.data()!['contacts']);
contacts = userData.data()!['contacts'];
}
Is it possible to download the data before the build method every time? I can't use the straembuilder because it's already in use for another download.
create a variable bool isLoading = true
getUserData() {
//await retrieve data
//after data is retrieved
setState(() {
isLoading = false;
});
}
In your build method:
isLoading ? Container(child: Text('Loading...')) : YourScreen()

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.

Rebuild widgets when a subcollection recieves data in another widget

I have 6 widgets that all share the same data with only one receiving the data from the stream
AllClasses() //displays all of classes and is the start of getting the data from the stream
ClassList(data) //displays nothing but builds the list view and holds the index of what class is to be displayed in classtile widget
ClassTile(data) //presents the data from the index of the data passed in from ClassList
ClassPage(data) //when a classtile is clicked this screen is presented
HomeworksList(data) //goes to this widget when the homeworks button is clicked shows all the homeworks that are available for the specific class clicked on
HomeworkTile(data) // shows the tile for each homework
As you can see I get the data once then pass it down the widget tree multiple times. What has become an issue is that when I am showing the homework list which is 5 widgets down the widget tree from where the data is coming from I have the option to add another homework to the homework list. technically adding it to the firestore database.
The problem is that when I add a Homework it does not refresh the data or rebuild any widgets until I go all the way back to the "AllClasses()" widget.
so to reiterate, I have a widget that shows all of the homeworks and in this widget you are able to add a homework and when you do it updates the subcollection, but does not rebuild the widget that shows all of the homeworks until I go back a couple widgets from where the data is first grabbed
the database schema is set up like this:
Classes {
subcollections:
Homework {
...
}
fields:
}
perhaps its the way I have my database and instead I should have a Homeworks collection that is outside of everything. or maybe its because I am using a generator future to produce the same thing as a stream by using a never ending while loop. either way I don't understand streams enough to understand what to do next, I have been working on this since 8am today and I have tried just about everything in my power. I may just use Homeworks as a seperate collection instead of a subcollection the problem with this is knowing which class I am in when I click on homework.
Stream<List<ClassData>> get fetchClassdata async* {
QuerySnapshot snapshot = await classesCollection.getDocuments();
// while (true) {
List<ClassData> _classList = List<ClassData>();
await Future.forEach(snapshot.documents, (element) async {
QuerySnapshot pre = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Pre")
.getDocuments();
QuerySnapshot homeworksSnapshot = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Homework")
.getDocuments();
QuerySnapshot notesSnapshot = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Notes")
.getDocuments();
QuerySnapshot testsSnapshot = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Tests")
.getDocuments();
List<Preq> _preList = List<Preq>();
List<Homework> _homeworkList = List<Homework>();
List<Note> _notesList = List<Note>();
List<Test> _testsList = List<Test>();
pre.documents.forEach((preClass) {
Preq preqData = Preq.fromMap(preClass.data);
if (preClass.data != null) {
_preList.add(preqData);
}
});
homeworksSnapshot.documents.forEach((hwork) {
Homework homeworkdata = Homework.fromMap(hwork.data);
_homeworkList.add(homeworkdata);
});
ClassData data = ClassData.fromMap(
element.data, element.documentID, _preList, _homeworkList);
if (data != null) {
_classList.add(data);
}
});
yield _classList;
// }
}
}

How to get list of documents and not listen for changes in flutter cloud firestore?

My application is again fetching list of items from firestore whenever I make a sort locally.
Due to which I am losing my sorted list and getting the original list back again.
Essentially, I am looking for a .once() alternative as I used in firebase realtime db with JS.
fetchItemsFromDb().then((itemsFromDb) {
setState(() {
items = itemsFromDb;
isProcessed = true;
});
});
fetchItemsFromDb() async {
List<Item> items = [];
await Firestore.instance.collection('items').getDocuments().then((data) {
items = data.documents.map((DocumentSnapshot item) {
var i = item.data;
return Item(
i['itemName'],
int.parse(i['quantity']),
int.parse(i['price']),
i['veg'],
i['uid'],
i['timestamp'],
LatLng(i['location'].latitude, i['location'].longitude),
);
}).toList();
});
return items;
}
FetchItemsFromDB() should be working how you expect it to, it could be that the code calling the function:
fetchItemsFromDb().then((itemsFromDb) {
setState(() {
items = itemsFromDb;
isProcessed = true;
});
});
is being run again when you do not expect it. Does that code live in a build method? If so it will run anytime the widget it is in rebuilds, which depending on how you are doing your local sort may be happening. If you only need it to run once maybe add it to the initState() function for the widget.

Initialising variable in initState

I'm initialising a variable in initState(){}:
#override
void initState() {
getDataFromFirestore();
super.initState();
});
}
The method is asyn and basically gets data from Firestore to populate an object '_markerMap' with data. This property is then used as a property in a widget. And this widget is called in my build method.
Widget build(BuildContext context) {
return new Scaffold(
body: MyWidget(
markerMap: _markerMap)
);
....
}
MyWidget is a calendar. markerMaps adds icons to certain dates on the calendar. The markers are only sometimes added to the calendar. So the failure is intermittent. Is is safe to assume that in initState() the data will load from firestore to initialise the variable from Firestore. Any thoughts what might be happening and why only sometimes the markers show up on the calendar?
adding code where _markerMap is set
getDataFromFirestore() async {
await FirebaseAuth.instance.currentUser().then((user) {
Firestore.instance.collection('availableDates').where('bandId', isEqualTo: user.uid).snapshots().listen(
(data) => data.documents.forEach((doc) => _markerMap.add(
doc['availableDates'].toDate(),
Event(
date:doc['availableDates'].toDate(),
title: 'hello',
icon: _presentIcon(doc['availableDates'].toDate().day.toString())))));
setState(() {});
}).catchError((onError){
});
}
As I can see from your getDataFromFirestore method, you perform the widget rebuild (setState call) right after you get the User object (FirebaseAuth.instance.currentUser() call).
However, you modify _markerMap variable later on - only when your Firestore.instance.collection query is complete. In this case setState call where it is right now is redundant.
Calling setState inside of your listen callback should solve the problem.
e.g.
final _markerMap = {};
getDataFromFirestore() async {
await FirebaseAuth.instance.currentUser().then((user) {
Firestore.instance
.collection('availableDates')
.where('bandId', isEqualTo: user.uid)
.snapshots()
.listen((data) => data.documents.forEach((doc) {
setState(() { // Here is the setState call
_markerMap.add(
doc['availableDates'].toDate(),
Event(
date: doc['availableDates'].toDate(),
title: 'hello',
icon: _presentIcon(doc['availableDates'].toDate().day.toString())
)
);
});
}));
}).catchError((onError) {});
}
Please double check this example code. My general advice is correct, however I did not test this on my end.
There's nothing wrong with initializing variables in initState unless they are static. So whats happening is you have declared _markerMap initially but it will only get initialed after some async function getDataFromFirestore(), which could take few seconds. And here you have assigned markerMap: _markerMap but initially _markerMap is null and only get initialed after getDataFromFirestore() function. Therefore its a good practice to either check for null
_markerMap!=null?MyWidget(
markerMap: _markerMap):Container();
or provide a default value

Resources