How to read data from firebase inside AlertDialog widget? - firebase

I am trying to read data from firebase inside an AlertDialog in flutter, when a button is pressed, and then update it afterwards.
I have tried using a StreamBuilder, but nothing happens
new FlatButton(
child: const Text('+ Add'),
onPressed: () {
StreamBuilder(
stream: Firestore.instance.collection('users').document(user.uid).collection('Filtre').document('ChooseSelf').snapshots(),
builder: (context, snapshot) {
var TypeSelfFilters = snapshot.data;
List<String> ListOfTypeSelf = List.from(TypeSelfFilters["Personer"]);
ListOfTypeSelf.add("value of TextFormField");
Firestore.instance.collection('users').document(user.uid).collection('Filtre').document('ChooseSelf').updateData({'Personer': ListOfTypeSelf});
}
);
Navigator.pop(context);
}
);
I do not get any errors, but the code inside the StreamBuilder is not executed for some reason.
Thank You

Hm... It looks to me that you are expecting to get the data when the use taps on FlatButton.
Let's look what happens:
tap on FlatButton
Instantiate a StreamBuilder
Start getting data from Firestore
Do some Firestore magic, update date
Then close dialog by navigator.pop()
Problem: you call navigator.pop() right after Instantiation of StreamBuilder. StreamBuilder has to wait somewhat to get the data. If you pop a route, and with that destroying your alert dialog, the builder callback will not be called. So the actual sequence of things happening is: Tap -> Instantiate StreamBuilder -> pop route
Recommendation: why wrap your computation in a StreamBuilder? You could do:
onPressed: () {
Firestore.instance.collection('users')/*...*/.snapshots().then((snapshot) async {
// then branch is executed once snapshot is retrieved from firestore
var TypeSelfFilters = snapshot.data;
// do some more computation and magic
await Firestore.instance.collection/*...*/.updateData();
// wait for updateData to finish
Navigator.pop(context); // this context is not the context inside the StreamBuilder
});
}

Thanks to Daniel V. i found a solution:
var myData = Firestore.instance.collection('users').document(user.uid).collection('Filtre').document('ChooseSelf').snapshots().first;
myData.then((snapshot) async {
var TypeSelfFilters = snapshot.data;
List<String> ListOfTypeSelf = List.from(TypeSelfFilters["Personer"]);
ListOfTypeSelf.add("bare en test");
Firestore.instance.collection('users').document(user.uid).collection('Filtre').document('ChooseSelf').updateData({'Personer': ListOfTypeSelf});
Navigator.pop(context); // this context is not the context inside the StreamBuilder
});
}
)

Related

How to get document ID for a specific document?

I would like to get the document id of a specific document when I put its name in a function.
Here is my function:
void _getId(town) {
var data = FirebaseFirestore.instance
.collection('towns')
.where('town_name', isEqualTo: town)
.snapshots();
print(data.id);}
Here is my button:
ElevatedButton(
onPressed: () => _getId(_townChosen), child: Text('Get ID')),
The value _townChosen is a string which corresponds to the field town_name in the database. In the complete program, I get the value from a dropdown button, this part works well.
Here is the database
All documents have an town_name field.
I need the id of the chosen town and send it in others widgets to use its subcollection. Please can you help me to get the id?
First, create a variable called townid, and change your function to async, and use a stateful widget to update it, and use get instead of snapshots:
String townId = 'Get ID';
void _getId(town) async {
var data = await FirebaseFirestore.instance
.collection('towns')
.where('town_name', isEqualTo: town)
.get();
setState(() {
townId = data.docs[0].id; //because the query returns a list of docs, even if the result is 1 document. You need to access it using index[0].
});
print(townId);
}
In your button:
ElevatedButton(onPressed: () => _getId(_townChosen), child: Text(townId)),

Run Firebase Cloud Function before page load Flutter

I have a Firebase Cloud Function that creates a document when a new user signs up. The document that gets created by the function is where the user data will be stored. The process is as such:
User signs up
User document created in Firestore
Firebase Function triggered to create 'other' document
User sees homepage
Homepage uses data from 'other' document
The problem I have is the user is going straight to the homepage before the Firebase Function is executed and the 'other' document is not created yet.
This means the user is just seeing a CircularProgressIndicator because the page is loading before the 'other' document exists.
It works fine if the user clicks away from that page and returns to it, because by that time the 'other' document exists. Likewise, when I add a 5 second delay on initially loading the homepage, it works because the Firebase Function has time to execute - but this is not a nice solution.
I am wondering how I can ensure the Firebase Function has executed and the 'other' document created before loading the homepage?
initState
void initState() {
super.initState();
final user = Provider.of<UserClass>(
context,
listen: false);
final uid = user.uid;
_houseID = getHouseID(uid);
}
Future returning ID of document created by Firebase Function
Future<String> getHouseID(uid) async {
String houseID;
await Future.delayed(Duration(milliseconds: 5000)); // with this delay it works fine
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.collection('userHouses') // this collection is being created by a Cloud Function
.get()
.then(
(value) {
houseID = value.docs.single.id;
},
);
return houseID;
}
FutureBuilder
return FutureBuilder(
future: _houseID,
builder: (BuildContext context, AsyncSnapshot snapshot) {
hhid = snapshot.data;
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator()); // this runs forever when the user first signs up
} else {
return // homepage using hhid to retrieve user data
You can open a stream which listens to that specific document after the user signs up. The stream initially may be empty, so you can check if the document exists. Once the document is written, the stream will be updated and then you can close it if you're done.
here's a simple code that explains the idea:
final subscription = FirebaseFirestore.instance.doc('path-to-document').snapshots().listen((event) {
if (event.exists) {
// do something with the data
final data = event.data();
// update your state
// .... some code
// call a function to close the subscription if you don't need it
closeSubscription();
}
});
closeSubscription() {
subscription.cancel();
}

How to store data of collection from firestore into a list?

I am looking to store fetch data from firestore into a List which would contain data from all of its documents.
I defined list as :
List retrievedData = List();
next, on press of button, I wanted to print data in all documents of a specific collection. So, I did this:
RaisedButton(
onPressed: () async {
var collectionReferece = await Firestore.instance.collection('insults');
collectionReferece.getDocuments().then((collectionSnapshot){
retrievedData = collectionSnapshot.documents.toList();
});
print(retrievedData);
},
I am expecting this in console:
I/flutter (11351): [{index: 200, title: This is a test 1},{index: 100, title: This is a test 2}]
But I get this:
I/flutter (11351): [Instance of 'DocumentSnapshot', Instance of 'DocumentSnapshot']
Also, I just want to store this data in a list or any other variable. Help me out. Thank you.
Edit:
I tried to use forEach but it keeps on adding on every press of button.
If you want to:
retrieve data from firestore
add to list
create listview.builder
Then you can do the following, first declare the following variables under your State class:
class _MyHomePageState extends State<MyHomePage> {
bool isFirstTime = false;
List<DocumentSnapshot> datas = List<DocumentSnapshot>();
Next, create a method called getData() which will be referenced in onPressed:
floatingActionButton: FloatingActionButton(
onPressed: getData,
tooltip: 'Increment',
child: Icon(Icons.add),
),
getData() async {
if (!isFirstTime) {
QuerySnapshot snap =
await Firestore.instance.collection("insults").getDocuments();
isFirstTime = true;
setState(() {
datas.addAll(snap.documents);
});
}
}
Here on press of the FAB, you will get the data inside the insults collection. We use the boolean to only retrieve once per click. Inside the method dispose which you override:
#override
void dispose() {
super.dispose();
this.isFirstTime = false;
}
}
You can assign isFirstTime to false again. Then to display the data, You can use the property body of AppBar, assign it to Center widget, and the Center widget will contain the listview:
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: ListView.builder(
itemCount: datas.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${datas[index]["index"]}'),
subtitle: Text('${datas[index]["title"]}'),
);
},
),
Using listview.builder, you will have a list in your screen and you dont have to use forEach to iterate a list. You just have to use the get operator [] to be able to get the data inside the list.
Any code that needs access to the data from Firestore, need to be inside the then. So:
var collectionReferece = await Firestore.instance.collection('insults');
collectionReferece.getDocuments().then((collectionSnapshot){
retrievedData = collectionSnapshot.documents.toList();
print(retrievedData);
});
But you'll typically want to separate the data loading out of the build() method, and use state or FutureBuilder to get the data from the database into the rendered output. Some examples of that:
Flutter/Firebase_Auth: a build function returned null for using state
How to use one field of firebase to login for another example of using state
how do i call async property in Widget build method for an example of using a FutureBuilder
i think its because the .toList() method put those 2 documents just same datatype as "DocumentSnapshot" in the List. try printing this to be sure.
print(retrievedData[0]['title']);

Flutter, Is it possible to use Firestore Stream Builder in function without a widget build method?

I have a function that gets the results of a google places search for stores and searches Firestore to see if the store is already in the database.
Whenever I run it thought the Stream Builder is doing nothing.
I think the problem is that the function is not within a widget and does not have a build method. Here is the code:
void searchStores() async {
Prediction newStore = await PlacesAutocomplete.show(
context: context,
apiKey: kGoogleApiKey,
mode: Mode.overlay,
language: "en",
components: [new Component(Component.country, "au")]);
await places.getDetailsByPlaceId(newStore.placeId).then((detailStoreInfo) {
print('running 1');
StreamBuilder (
stream: Firestore.instance.collection('stores').document(detailStoreInfo.result.id).snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot){
print('running 2');
setState(() {
if (snapshot.connectionState == ConnectionState.active &&
snapshot.hasData){
print('running 3');
if (snapshot.data['veganOnly'] == null || snapshot.data['veganOnly'] == false){
print('running 4');
setState(() {
firstStore = true;
});
}
}
});
return null;
}
);
});
}
As you can see I added print statements to work out where my code is failing. 'running 1' is show so the places is returning a response.
But none of the other statements (2,3 or 4) are printing so it seems the stream builder is not working, not even coming back with null values.
I also tried putting a return before the stream builder but that had no effect either.
Am I correct in thinking this because it is not in the build method of a widget or is it something else entirely.
Thanks
A StreamBuilder is a Widget and as such, has to be inserted somewhere in your widget tree, just as you would for a Text widget. Just pass it a stream and return another widget inside it’s builder callback
You need to use the Stream class for it, for instance to get all details about a collection named "users", you can use,
Stream<QuerySnapshot> stream = _db.collection("users").snapshots();
stream.forEach((QuerySnapshot element) {
if(element == null)
return;
for(int count=0;count<element.documents.length;count++) {
print(element.documents[count].data.toString());
}
});

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