flutter Why doesn't async and await work? - firebase

_getstockList
_getstockList( List<dynamic> nlist) async {
Map<String, dynamic> userdocdata;
var userdata = await firestore.collection('users').doc('NVPjZEAZneKblrubGZSW').get();
userdocdata = userdata.data() as Map<String, dynamic>;
nlist = userdocdata['favorite'];
}
Main Code
Widget build(BuildContext context) {
List<dynamic> list = [];
List<Map<String, dynamic>> stockcardlist = [];
_getstockList(list);
print(list); // output
_getstockInfo(list, stockcardlist);
~~~
}
_getstockInfo
_getstockInfo(List<dynamic> nlist, List<Map<String,dynamic>> stockcardlist){
print(nlist.length); // output
}
Desired result
print(list)
print(nlist.length)
valid value
BUT
result
print(list) = []
print(nlist.length) = 0
please help me i use Future, sync, unawait but i cant solve

It looks like _getStockList doesn't return anything. When you pass it, the original object remains unaffected. You could try to fix that:
Future<List<dynamic>> _getstockList() async {
Map<String, dynamic> userdocdata;
var userdata = await firestore.collection('users').doc('NVPjZEAZneKblrubGZSW').get();
userdocdata = userdata.data() as Map<String, dynamic>;
List<dynamic> nlist = userdocdata['favorite'];
return nlist;
}
Now you need to call this function to fill the list
Widget build(BuildContext context) {
List<Map<String, dynamic>> stockcardlist = [];
List<dynamic> list = await _getstockList(); // DOES NOT WORK!
print(list);
_getstockInfo(list, stockcardlist);
...
}
However, this does not work, since you are not allowed to use await in build, which is not async. To get around this, you can use a FutureBuilder:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Widget build(BuildContext context) {
return FutureBuilder<List<dynamic>>(
future: _getstockList(),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
List<dynamic> list = snapshot.data ?? [];
print(list);
return MyAwesomeScreenWithAList(list);
}
);
}
Now if you want to use the results of the first async function in a second one, the easiest will probably be, though not ideal, to use two nested FutureBuilders.

Related

Flutter and Firebase Add Objects to List

this is my first question.
Right now my code is working when adding 'displayName' to List:
final usersRef = FirebaseFirestore.instance.collection('users');
List userList = [];
void getUsers() async {
List<String> newUserList = [];
await usersRef.get().then((QuerySnapshot snapshot) {
snapshot.docs.forEach((DocumentSnapshot documentSnapshot) {
Map<String, dynamic> data = documentSnapshot.data();
newUserList.add(data['displayName']);
});
});
setState(() {
userList = newUserList;
});
}
But I want to add 'userList[id]' AND 'userList[displayName]' to list the 'displayName' in DropDown BUT to send(push) the 'id':
DropdownSearch<String>(
...
items: userList,
...
onChanged: (String value) {
Navigator.push(context,MaterialPageRoute(builder: (context) => ProfilePage(profileId: value),
...
Because in DropDown-List Names could be duplicate like John Doe but a List of unique IDs to select makes no sense ... Any help?
The await in await usersRef.get()... is not doing anything, since you're not returning anything from within the callback.
I highly recommend using either async/await or then, but not both.
With then the code would be:
void getUsers() {
List<String> newUserList = [];
usersRef.get().then((QuerySnapshot snapshot) {
snapshot.docs.forEach((DocumentSnapshot documentSnapshot) {
Map<String, dynamic> data = documentSnapshot.data();
newUserList.add(data['displayName']);
});
setState(() {
userList = newUserList;
});
});
}
And with async/await it'd be:
void getUsers() async {
List<String> newUserList = [];
QuerySnapshot snapshot = await usersRef.get();
snapshot.docs.forEach((DocumentSnapshot documentSnapshot) {
Map<String, dynamic> data = documentSnapshot.data();
newUserList.add(data['displayName']);
});
setState(() {
userList = newUserList;
});
}

Flutter Firestore doc get returning null

I am trying to get a document from a Firestore collection using the following code:
firebase_service.dart:
class FirebaseService {
final firestoreInstance = FirebaseFirestore.instance;
final FirebaseAuth auth = FirebaseAuth.instance;
Map<String, dynamic> getProfile(String uid) {
firestoreInstance.collection("Artists").doc(uid).get().then((value) {
return (value.data());
});
}
}
home_view.dart:
Map<String, dynamic> profile =
firebaseService.getProfile(auth.currentUser.uid);
When stepping through the code the profile variable is null in home_view.dart, but value.data() in firebase_service.dart contains a map. Is there a reason why this value isn't being returned in home_view.dart?
Your code needs a few edits, as the getProfile function is async.
class FirebaseService {
final firestoreInstance = FirebaseFirestore.instance;
final FirebaseAuth auth = FirebaseAuth.instance;
// set the return type to Future<Map<String, dynamic>>
Future<Map<String, dynamic>> getProfile(String uid) async { // insert async here
/// insert a return and await here
return await firestoreInstance.collection("Artists").doc(uid).get().then((value) =>
return value.data(); // the brackets here aren't needed, so you can remove them
});
}
}
Then finally in home_view.dart
// insert await here:
Map<String, dynamic> profile = await
firebaseService.getProfile(auth.currentUser.uid);
If you plan to use the getProfile function I suggest you to use a FutureBuilder.
In you home_view.dart's build function write this:
return FutureBuilder(
future: firebaseService.getProfile(auth.currentUser.uid),
builder: (context, snapshot){
if (!snapshot.hasData){
return Center(child: CircularProgressIndicator(),);
}
final Map<String, dynamic> profile = snapshot.data.data();
return YourWidgets();
});
And now you don't need to write:
Map<String, dynamic> profile = await
firebaseService.getProfile(auth.currentUser.uid);
This is an async operation and you have to await for its value.
For reference, you can take a look here at documentation of how propper authentication and CRUD operations made in Firebase with flutter.

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

flutter firebase get array from Firestore and assign it to a list

I have a collection in Firestore. It has a field array i am trying to get the array from Firestore and assign it to a list in flutter.
My collection is as below
My code for getting data from Firestore
List<Offset> pointlist = <Offset>[];
getdata() async{
await Firestore.instance.collection("points").document('biZV7cepFJA8T6FTcF08').get().then((value){
setState(() {
List<Offset> pointlist = List.from(value.data['point']);
});
});
}
#override
void initState() {
super.initState();
getdata();
}
i get this error type 'String' is not a subtype of type 'Offset'
The thing which you are doing wrong is this:
// You have initialised your List as a Offset Object Type
List<Offset> pointList;
Secondly, the data you are assigning is a String, if you closely take a look at that firebase.
"Offset(x,y)"
Finally, trying to assign the String value to a List of type Offset class/object
If you want to make the thing works, then either make the List of type String and then add it to the List
List<String> pointlist = List.from(value.data['point']);
Or first Add the data to the Offset Object like this, and then pass it to the List
List<Offset> pointList = <Offset>[];
getdata() async{
await Firestore.instance.collection("points").document('biZV7cepFJA8T6FTcF08').get().then((value){
setState(() {
// first add the data to the Offset object
List.from(value.data['point']).forEach((element){
Offset data = new Offset(element);
//then add the data to the List<Offset>, now we have a type Offset
pointList.add(data);
});
});
});
}
SUMMARY
Always look for the data type you are giving to the List, if you are trying to add the data which is not a type T of List<T>, you will always get this error of type mismatch. Hope this will give you some clarity and some basic idea about programming. Keep learning :)
you have to declare list and all list item will be store in declared list so you can access it.
List<dynamic> alldata =[];
Future<QuerySnapshot?> getData() async {
dataofItem = FirebaseFirestore.instance
.collection('$data')
.get()
.then((QuerySnapshot? querySnapshot) {
querySnapshot!.docs.forEach((doc) {
allData = doc["item_text_"];
print("allData = $allData");
// print("getData = ${doc["item_text_"]}");
});
});
return dataofItem;
}
Lets assume the structure to be.
...
arrayOfString
|_element1
|_element2
|_element3
...
Use the following code to get the array elements of arrayOfString parameter
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
Map<String, dynamic> data =
snapshot.data!.data()! as Map<String, dynamic>;
return ListView(
children: data['arrayOfString'].map<Widget>((e) { 👈 arrayOfString has array of Strings
return ListTile(
title: Text(e.toString()), // 👈 printing every string
);
}).toList());
},
),
),
);
}

Get all from a Firestore collection in Flutter

I set up Firestore in my project. I created new collection named categories. In this collection I created three documents with uniq id. Now I want to get this collection in my Flutter application so I created CollectionReference:
Firestore.instance.collection('categories')
but I don't know what next.
I am using this plugin firebase_firestore: 0.0.1+1
This is the easiest way to get all data from collection that I found working, without using deprecated methods.
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('collection');
Future<void> getData() async {
// Get docs from collection reference
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
}
Here is the code if you just want to read it once
QuerySnapshot querySnapshot = await Firestore.instance.collection("collection").getDocuments();
var list = querySnapshot.documents;
Using StreamBuilder
import 'package:flutter/material.dart';
import 'package:firebase_firestore/firebase_firestore.dart';
class ExpenseList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("expenses").snapshots,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text("There is no expense");
return new ListView(children: getExpenseItems(snapshot));
});
}
getExpenseItems(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map((doc) => new ListTile(title: new Text(doc["name"]), subtitle: new Text(doc["amount"].toString())))
.toList();
}
}
I was able to figure out a solution:
Future getDocs() async {
QuerySnapshot querySnapshot = await Firestore.instance.collection("collection").getDocuments();
for (int i = 0; i < querySnapshot.documents.length; i++) {
var a = querySnapshot.documents[i];
print(a.documentID);
}
}
Call the getDocs() function, I used build function, and it printed all the document IDs in the console.
As of 2021, there have been some major changes in the cloud_firestore package. I was working with firestore on a project, and found that none of the old tutorials were working due to the API changes.
After going through documentation and a few other answers on Stack, here's the solution for the same.
The first thing that you need to do is create a reference for your collection.
CollectionReference _cat = FirebaseFirestore.instance.collection("categories");
Next step is to query the collection. For this, we will be using the get method on the collection reference object.
QuerySnapshot querySnapshot = await _cat.get()
Finally, we need to parse the query snapshot to read the data from each document within our collection. Here, we will parse each of the documents as maps (dictionaries) and push them to a list.
final _docData = querySnapshot.docs.map((doc) => doc.data()).toList();
The entire function will look something like this:
getDocumentData () async {
CollectionReference _cat = FirebaseFirestore.instance.collection("categories");
final _docData = querySnapshot.docs.map((doc) => doc.data()).toList();
// do any further processing as you want
}
QuerySnapshot snap = await
Firestore.instance.collection('collection').getDocuments();
snap.documents.forEach((document) {
print(document.documentID);
});
Update:
One time read of all data:
var collection = FirebaseFirestore.instance.collection('users');
var querySnapshot = await collection.get();
for (var doc in querySnapshot.docs) {
Map<String, dynamic> data = doc.data();
var fooValue = data['foo']; // <-- Retrieving the value.
}
Listening for all data:
var collection = FirebaseFirestore.instance.collection('users');
collection.snapshots().listen((querySnapshot) {
for (var doc in querySnapshot.docs) {
Map<String, dynamic> data = doc.data();
var fooValue = data['foo']; // <-- Retrieving the value.
}
});
For me it works on cloud_firestore version ^2.1.0
Here is the simple code to display each colection in JSON form. I hope this would help someone
FirebaseFirestore.instance.collection("categories").get().then(
(value) {
value.docs.forEach(
(element) {
print(element.data());
},
);
},
);
the easiest way to retrieve data from the firestore is:
void getData() async {
await for (var messages in _firestore.collection('collection').snapshots())
{
for (var message in messages.docs.toList()) {
print(message.data());
}
}
}
what If you store data in the docs Id ?
if the doc is EMPTY, it would be IMPOSSIBLE to get the id doc, its a bug, unless you set a field in a specific doc
enter image description here
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
final database1 = FirebaseFirestore.instance;
Future<QuerySnapshot> years = database1
.collection('years')
.get();
class ReadDataFromFirestore extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future: years,
builder: (context, snapshot) {
if (snapshot.hasData) {
final List<DocumentSnapshot> documents = snapshot.data.docs;
return ListView(
children: documents
.map((doc) => Card(
child: ListTile(
title: Text('doc.id: ${doc.id}'),
//subtitle: Text('category: ${doc['category']}'),
),
))
.toList());
} else if (snapshot.hasError) {
return Text(snapshot.error);
}
return CircularProgressIndicator();
}
);
}
}

Resources