Firebase: Iterate over map - firebase

I got a collection called "inventar" which contains a doc with an auto generated value which contains a single map I want to iterate about.
Note that the keys are going to vary, because the user is going to specify it.
How can I iterate over this map so that I can output the key and value in my table cells listed below?
new StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("inventar")
.where("verfallsdatum")
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new Table(
children: [
new TableRow(children: [
new TableCell(child: new Text("Produkt")),
new TableCell(child: new Text("Verfallsdatum")),
]),
// how to iterate here?
new TableRow(
children: [
new TableCell(
child: new Text("key"),
),
new TableCell(
child: new Text("value"),
),
]
)
]);
},
)
Edit:
I am trying to get this data out of my database since alomost one month! Which major mistake or misunderstanding do I have, that I am unable to query single document which contains a map and output it as table? Is this task so awefuly rough to perform or am I just dumb? :D
Here is the recent attampt I did, but it says there is no method "forEach" for the type "DocumentSnapshot" even though I think I say this attampt in pretty much every tutorial. But mine aint work!
var products = await db.collection("inventar").doc("vqQXArtqnFyAlkPC1PHr").get().then((snapshot) => {
snapshot.forEach((doc) => {
})
});

return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("inventar")
.where("verfallsdatum")
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
children: snapshot.data.docs.map((documentSnapshot) {
var _data = documentSnapshot.data();
return _data.map((key, value) {
return new Table(children: [
new TableRow(children: [
new TableCell(child: new Text("Produkt")),
new TableCell(child: new Text("Verfallsdatum")),
]),
// how to iterate here?
new TableRow(children: [
new TableCell(
child: new Text(key),
),
new TableCell(
child: new Text(value),
),
])
]);
}).toList();
}).toList(),
);
},
);

Related

Is there anyway to sort incoming data from StreamBuilder?

Stream<QuerySnapshot> _usersStream = FirebaseFirestore.instance.collection('Listings').snapshots();
I am using this stream.
And i have streambuilder like this.
Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
String start_location=data['start_location'];
String end_location=data['end_location'];
String date=data['date'];
String time=data['time'];
String price=data['price'];
String name_surname=data['name_surname'];
String userId=data['user_id'];
String coord=data['coord'];
var splitted=coord.split('/');
for(int i=0; i<splitted.length-1; i++){
String x = splitted[i];
var splitted2=x.split('-');
double result=calculateDistance(widget.place_latlng.latitude, widget.place_latlng.longitude, double.parse(splitted2[0]), double.parse(splitted2[1]));
if(result<1 && start_location!= widget.placename){
print("Found!");
return GestureDetector(
onTap: (){
//onTap func
},
child: buildTripCard(
context,
start_location: start_location,
end_location: end_location,
date: date,
time: time,
price: price,
name_surname: name_surname,
userId: userId,
),
);
}
}
return Container();
}).toList(),
);
},
),
)
I put into the incoming data to calculateDistance function. It returns double value (result).If that value less than 1, it shows in ListView. What i want to do is, sort Data which shows in listView order by result value.
How can i reach list that i created with .toList() method?
Sort data by chaining into stream. The below example takes all photos, which title starts with p. Converting Future to Stream is just for demonstration.
Stream<List<Photo>> photos() {
return Stream.fromFuture(
http.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'))
).map((r) => parsePhotos(r.body))
.map((list) => list.where((p) => p.title.startsWith('p')).toList());
}
Then use ListView.builder to render the final list.
StreamBuilder<List<Photo>>(
stream: photos(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error has occurred!'),
);
} else if (snapshot.hasData) {
final photos = snapshot.data!;
return ListView.builder(
itemCount: photos.length,
itemBuilder: (context, index) {
return Text(photos[index].title);
},
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
}

The getter 'docs' isn't defined for the type 'AsyncSnapshot<Object?>'

After migrating to null safety getting error The getter 'docs' isn't defined for the type 'AsyncSnapshot<Object?>'.
Try importing the library that defines 'docs', correcting the name to the name of an existing getter, or defining a getter or field named 'docs'.
Code snippet where error is
return FutureBuilder(
future: searchResultsFuture,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<UserResult> searchResults = [];
snapshot.docs.forEach((doc) { //have error here
User user = User.fromDocument(doc);
UserResult searchResult = UserResult(user);
searchResults.add(searchResult);
});
return ListView(
children: searchResults,
);
},
);
}
searchResultsFuture
handleSearch(String query) {
Future<QuerySnapshot> users =
usersRef.where("displayName", isGreaterThanOrEqualTo: query).get();
setState(() {
searchResultsFuture = users;
});
}
clearSearch() {
searchController.clear();
}
The snapshot in your code is an AsyncSnapshot, which indeed doesn't have a docs child. To get the docs from Firestore, you need to use:
snapshot.data.docs
Also see the FlutterFire documentation on listening for realtime data, which contains an example showing this - and my answer here explaining all snapshot types: What is the difference between existing types of snapshots in Firebase?
change like this:
return FutureBuilder(
future: searchResultsFuture,
builder: (context, **AsyncSnapshot** snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<UserResult> searchResults = [];
**snapshot.data!.docs.forEach((doc) {**
User user = User.fromDocument(doc);
UserResult searchResult = UserResult(user);
searchResults.add(searchResult);
});
return ListView(
children: searchResults,
);
},
);
}
usually, it takes a few ms for data to retrieve so I tried this to
make sure my operations are performed after data is retrieved
return StreamBuilder<QuerySnapshot>(
stream: Collectionreference
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> activitySnapshot) {
if (activitySnapshot.hasError) {
return Center(
child: Text('Something went wrong'),
);
}
if (activitySnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: SpinKitWave(
color: constants.AppMainColor,
itemCount: 5,
size: 40.0,
)));
}
if (!activitySnapshot.hasData || activitySnapshot.data.docs.isEmpty) {
return Center(
child: Text('Nothing to Display here'),
);
}
if (activitySnapshot.hasData) {
activitySnapshot.data.docs.forEach(doc=>{
print(doc);
})
}
}
});

query geoflutterfire as firestore

I am trying to query firebase with the following requirements:
If I have a location, I want to receive the data in a certain radius through the GeoFlutterfire plugin.
If I don't have a location, I want to receive data with a limit.
I don't know if I do it correctly, but I am having problems preparing the stream.
With firestore's .snapshot() method, I get a Stream<QuerySnapshot>, but with Geoflutterfire's collection(...).Within(...) method, I get a Stream <List <DocumentSnapshot>>. This gives me trouble when trying to display the data.
If anyone can clear my mind, I would be very grateful.
Thanks in advance.
Consumer<LocationProvider>(
builder: (_, location, __) {
if (location.loading) {
return Center(
child: CircularProgressIndicator(),
);
}
final ref =
FirebaseFirestore.instance.collection("publicaciones");
Stream<List<DocumentSnapshot>> stream;
final pos = location.direccion?.geoPoint;
if (pos != null) {
final geo = Geoflutterfire();
stream = geo.collection(collectionRef: ref).within(
center: geo.point(
latitude: pos.latitude,
longitude: pos.longitude,
),
field: "direccion.geoHash",
radius: radio,
);
}
return StreamBuilder(
stream: stream ?? ref.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
print(
"Error recibiendo publicaciones cercanas: ${snapshot.error}");
return Text("Error recibiendo publicaciones.");
}
if (snapshot.hasData) {
final List<Publicacion> publicaciones = snapshot.data
.map((p) => Publicacion.fromSnapshot(p))
.toList();
if (publicaciones.length == 0) {
return Text("No hay publicaciones cercanas.");
}
return ListView.builder(
shrinkWrap: true,
itemCount: publicaciones.length,
itemBuilder: (BuildContext context, int index) {
return Text(publicaciones[index].titulo);
},
);
}
return Center(
child: CircularProgressIndicator(),
);
},
);
},
)
PS: I know that stream: stream ?? ref.snapshots() is not the best way to program, but my head is a bit clouded today, sorry.
I don't know if the title of the post describes my problem, so if you can think of a better one, I'll change it.
SOLUTION:
Thanks to William Cardenas I have restructured my code as follows:
final ref =
FirebaseFirestore.instance.collection("publicaciones");
final pos = location.direccion?.geoPoint;
Stream<List<Publicacion>> stream;
if (pos != null) {
final geo = Geoflutterfire();
stream = geo
.collection(collectionRef: ref)
.within(
center: geo.point(
latitude: pos.latitude,
longitude: pos.longitude,
),
field: "direccion.geoHash",
radius: radio,
)
.map<List<Publicacion>>(
(snap) =>
snap.map((s) => Publicacion.fromSnapshot(s)).toList(),
);
} else {
stream = ref.snapshots().map<List<Publicacion>>((snap) =>
snap.docs.map((s) => Publicacion.fromSnapshot(s)).toList());
}
Then I used my stream:
return StreamBuilder<List<Publicacion>>(
stream: stream,
builder: (context, snapshot) {...
Map returns and iterable so we have to remember to add the "toList()" at the end.
With the Stream<List<DocumentSnapshots>> we could continue mapping the snapshots to a specific model class by the following:
stream.map((snapshot) {final data = snapshot.data();
return data != null ? Publicacion.fromSnapshot(data)
: null
}).toList();
And then try your streambuilder with something like this:
StreamBuilder<List<Publicacion>>(
stream: stream, builder: (context, snapshot) {
if(snapshot.hasData) { final pubs = snapshot.data;
final children = pubs.map((pub) =>
Text(pub.name)).toList();
return ListView(children: children);
} return Center(child: CircularProgressIndicator());
} if(snapshot.hasError){ return Center(child:
Text('Some error occurred'));
}return Center(child: CircularProgressIndicator());
)
In general do the data conversions before calling the streambuilder.

The argument type 'Iterable<Text>' can't be assigned to the parameter type 'Widget' in Flutter

I got an error on this line of my code and do not find any solution for that.
child: snapshot.data.docs.map((DocumentSnapshot document) {
The argument type 'Iterable' can't be assigned to the parameter type 'Widget'.dartargument_type_not_assignable
This is my code:
Widget buildCategtoryFirestore() => StreamBuilder(
stream: FirebaseFirestore.instance.collection('').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if(!snapshot.hasData) {
return null;
} else {
List<DropdownMenuItem> categoryItems= [];
for(int i=0;i<snapshot.data.docs.length;i++) {
//DocumentSnapshot snap=snapshot.data.docs[i];
categoryItems.add(
DropdownMenuItem(
child: snapshot.data.docs.map((DocumentSnapshot document) {
return Text(document['title']);
}),
value: "$document['title']",
),
);
return DropdownButtonFormField(
items: categoryItems,
);
}
}
},
);
Can anybody help why I got this error and how to fix it?
I found the solution in another community. I works with this code:
Widget buildCategtoryFirestore() => StreamBuilder(
stream: FirebaseFirestore.instance.collection('petertesthausen#gmail.com_categories').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
List<DropdownMenuItem> _categoryItems;
if (!snapshot.hasData) {
return Text("no data...");
} else {
_categoryItems = snapshot.data.docs.map((doc) {
return DropdownMenuItem(
child: Text(EncryptionDecryption.decryptAES(doc['title'])),
value: EncryptionDecryption.decryptAES(doc['title']),
);
}).toList();
return DropdownButtonFormField(
hint: Text('Kategorie'),
items: _categoryItems,
onChanged: (value) => {print(value)},
);
}
}
);
Your code returns an Iterable, not a single Widget which is expected by DropdownMenuItem.
snapshot.data.docs.map((DocumentSnapshot document) {
return Text(document['title']);
})
Maybe you can try to take the first one if you don't expect a list, e.g.:
snapshot.data.docs.map((DocumentSnapshot document) {
return Text(document['title']);
}).first
But be carefull if your list is empty, it will throw an error.
.map() returns an iterable. You will have to add .toList() for the error to go away.
Like this:
snapshot.data.docs.map((DocumentSnapshot document) {
return Text(document['title']);
}).toList();
However, you probably wanted to do something like this:
This is much more effective as it will loop through the items only once.
List<DropdownMenuItem> categoryItems = [];
for (int i = 0; i < snapshot.data.docs.length; i++) {
DocumentSnapshot snap=snapshot.data.docs[i];
categoryItems.add(
DropdownMenuItem(
child: Text('${snap.data()['title']}'),
value: snap.data()['title'],
),
);
return DropdownButtonFormField(
items: categoryItems,
);
}

Flutter rendering list from firebase based on condition in dropdown menu

I'm trying to figure out how to render a list from a specific collection in firebase and to change that list when selecting options from dropdown menu. I could get the list rendered on 1 collection, but when I add my dropdown menu, with the default value being 'lost', nothing is displayed. Here's what I have so far that works, but not entirely what I want.
class _ListPageState extends State<ListPage>{
List<String> _type = ['lost', 'found'];
String _selectedView = 'lost';
//this getData pulls from 'lost' collection, since I set _selectedView to lost by default
Future getData() async{
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection(_selectedView).getDocuments();
return qn.documents;
}
navigateToDetail(DocumentSnapshot post){
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage(post: post,)));
}
Widget _viewType() {
return new DropdownButtonFormField(
value: _selectedView,
onChanged: (newValue) {
setState(() {
_selectedView = newValue;
});
},
items: _type.map((view) {
return new DropdownMenuItem(
child: new Text(view),
value: view,
);
}).toList(),
);
}
#override
Widget build(BuildContext context){
return ListView(
children: <Widget>[
_viewType(),
FutureBuilder(//it's not rendering any of this when adding the dropdown above it
future: getData(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}
else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index){
return ListTile(
title: Text(snapshot.data[index].data["Title"]),
onTap: () => navigateToDetail(snapshot.data[index]),
);
});
}
}),]
);
}
}
Thanks in advance for any help.
Please let me know if there's any more code you'd like to see.
I this I have to wrap part of it with setState(), but I'm not quite sure where.
Thanks for the fast clarification.
What is happening here is that you have put a ListView inside a ListView. You should use a Column.
By default (as mentioned in the documentation):
The Column widget does not scroll (and in general it is considered an error to have more children in a Column than will fit in the available room). If you have a line of widgets and want them to be able to scroll if there is insufficient room, consider using a ListView.
In your case, you want to place a ListView that will overflow the Column that can't scroll. To avoid that, consider using an Expanded
to take the remaining space so that the height is somehow constrained and the ListView knows its limits and work properly.
class _ListPageState extends State<ListPage> {
List<String> _type = ['lost', 'found'];
String _selectedView = 'lost';
//this getData pulls from 'lost' collection, since I set _selectedView to lost by default
Future getData() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection(_selectedView).getDocuments();
return qn.documents;
}
navigateToDetail(DocumentSnapshot post) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
post: post,
)));
}
Widget _viewType() {
return new DropdownButtonFormField(
value: _selectedView,
onChanged: (newValue) {
setState(() {
_selectedView = newValue;
});
},
items: _type.map((view) {
return new DropdownMenuItem(
child: new Text(view),
value: view,
);
}).toList(),
);
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
_viewType(),
Expanded(
child: FutureBuilder(
//it's not rendering any of this when adding the dropdown above it
future: getData(),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Text("Loading"),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return ListTile(
title: Text(snapshot.data[index].data["Title"]),
onTap: () => navigateToDetail(snapshot.data[index]),
);
},
);
}
},
),
),
],
);
}
}

Resources