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.
Related
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(),
);
}
}
Hey I tried to get data from firestore, the data is there, but the stream return empty list, any help will be appreciated!
Here is my stream builder code:
StreamBuilder<List<ChatModel>>(
stream: ChatRoom.getChats(id),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
print(snapshot.error);
return Center(
child: Text('Something Went Wrong Try later'));
} else {
if (snapshot.hasData) {
final message = snapshot.data;
print(message);
return ListView.separated(
itemBuilder: (context, index) => messageWidget(
message![index].message,
message![index].source == _userId),
separatorBuilder: (context, index) => SizedBox(
height: SizeConfig.safeBlockVertical * 3),
itemCount: snapshot.data!.length);
} else
return Center(child: Text('Say Hi..'));
}
}
}),
here is how I call the firestore:
class ChatRoom {
String id;
ChatRoom({required this.id});
static Stream<List<ChatModel>> getChats(String id) =>
FirebaseFirestore.instance
.collection(id)
.orderBy(ChatField.sendAt, descending: false)
.snapshots()
.transform(Util.transformer(ChatModel.fromJson));
}
and here is how I transform the data:
static StreamTransformer<QuerySnapshot<Map<String, dynamic>>, List<T>> transformer<T>(
T Function(Map<String, dynamic> json) fromJson) =>
StreamTransformer<QuerySnapshot<Map<String, dynamic>>, List<T>>.fromHandlers(
handleData: (QuerySnapshot data, EventSink<List<T>> sink) {
final snaps = data.docs.map((doc) => doc.data()).toList();
final object = snaps.map((json) => fromJson(json as Map<String, dynamic>)).toList();
sink.add(object);
},
);
here is the model
static ChatModel fromJson(Map<String, dynamic> json) => ChatModel(
dest: json["dest"],
destName: json['destName'],
id: json['id'],
message: json["message"],
sendAt: json["sendAt"],
source: json["source"],
sourceName: json["sourceName"],
);
here is the screenshot of the firestore:
Hello You have to check snapshot data if its null or not please see below code hope this works for you
StreamBuilder<List<ChatModel>>(
stream: ChatRoom.getChats(id),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
print(snapshot.error);
return Center(
child: Text('Something Went Wrong Try later'));
} else {
if(snapshot.data != null){
final message = snapshot.data;
return snapshot.data.size > 0 ?
print(message);
ListView.separated(
itemBuilder: (context, index) => messageWidget(
message![index].message,
message![index].source == _userId),
separatorBuilder: (context, index) => SizedBox(
height: SizeConfig.safeBlockVertical * 3),
itemCount: snapshot.data!.size) :
Center(child: Text('Say Hi..'));
}
return Center(child: Text('Say Hi..'));
}
}),
Let me know if it's work.
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);
})
}
}
});
I am having extreme difficulty in handling this. I call an async function that will get some info from SQLite but I can't seem to get it. It just renders a empty screen in which should be a listview.
List allItems = new List();
Future<void> pegaDados() async {
var itens = await geraCardapio();
for (var i = 0; i < itens.length; i++) {
print((itens[i].toMap()));
allItems.add(itens[i].toMap());
}
}
print(pegaDados());
return ListView.builder(
itemCount: allItems.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset("assets/"+ allItems[index]['imagem'], fit: BoxFit.contain,),
title: Text(allItems[index]['pedido']),
trailing: Text(allItems[index]['valor']),
);
},
);
Thank you very much.
I managed to get the solution, thanks to both people who answered the question (using both solutions I managed to get this little frankenstein)
Future<dynamic> pegaDados() async {
var allItems = await geraCardapio();
return allItems.map((allItems) => allItems.toMap());
}
return FutureBuilder(
future: pegaDados(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
print(snapshot.data);
var objeto = [];
for (var i in snapshot.data) {
objeto.add(i);
}
print(objeto);
return Container(
child: ListView.builder(
itemCount: objeto.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset("assets/"+ objeto[index]['imagem'], fit: BoxFit.contain),
title: Text(objeto[index]['pedido']),
trailing: Text(objeto[index]['valor'].toString()),
);
},
),
);
} else if (snapshot.hasError) {
throw snapshot.error;
} else {
return Center(child: CircularProgressIndicator());
}
});
thanks to [Mohammad Assem Nasser][1] and [Eliya Cohen][2] for the help!
[1]: https://stackoverflow.com/users/11542171/mohammad-assem-nasser
[2]: https://stackoverflow.com/users/1860540/eliya-cohen
You should first understand what is Future operations (Future function in your case). Future operations are the operations which take time to perform and return the result later. To handle this problem, we use Asynchronous functions.
Asynchronous Function let your program continue other operations while the current operation is being performed. Dart uses Future objects (Futures) to represent the results of asynchronous operations. To handle these operations, we can use async/await, but it is not possible to integrate async and await on widgets. So it is quite tricky to handle futures in widgets. To solve this problem flutter provided a widget called FutureBuilder.
In FutureBuilder, it calls the Future function to wait for the result, and as soon as it produces the result it calls the builder function where we build the widget.
Here is how it should be:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List allItems = new List();
Future<List> pegaDados() async{
var items = await geraCardapio(); // TODO: Add this function to this class
for (var i = 0; i < items.length; i++) {
print((items[i].toMap()));
allItems.add(items[i].toMap());
}
return items;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: Text('Demo')),
body: FutureBuilder(
future: pegaDados(),
builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.done){
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset("assets/"+ snapshot.data[index]['imagem'], fit: BoxFit.contain,),
title: Text(snapshot.data[index]['pedido']),
trailing: Text(snapshot.data[index]['valor']),
);
},
),
);
}
else if(snapshot.hasError){
throw snapshot.error;
}
else{
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}
Here is the link to a short video that will explain FutureBuilder in a concise way.
I'm not sure how your widget tree looks like, but I'm assuming ListView is being built simultaneously with pegaDados. What you're looking for is a FutureBuilder:
Future<dynamic> pegaDados() async{
var items = await geraCardapio();
return items.map((item) => item.toMap());
}
...
FutureBuilder<dynamic>(
future: pegaDados(),
builder: (BuilderContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Uninitialized');
case ConnectionState.active:
case ConnectionState.waiting:
return Text('Awaiting result...');
case ConnectionState.done:
if (snapshot.hasError)
throw snapshot.error;
//
// Place here your ListView.
//
}
return null; // unreachable
}
im getting this two errors in my debug console (core_booster, getBoosterConfig = false) and (Could not reach Firestore backend.) In my firestore data i ve got a Collection "Recipes" and then in de documents i ve got each recipe with its own attribute.
Here i leave you a sneek peek of the code.
new StreamBuilder(
stream: Firestore.instance.collection('Recipes').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return const Center(child: CircularProgressIndicator());
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_buildListRecipe(context, snapshot.data.documents[index]),
);
});
Then in my _buildListRecipe I'm accessing each in the value of each recipe.
new Image.network(
document["firstImage"],
width: double.infinity,
height: 150.0,
fit: BoxFit.cover,
),
did you resolve your issue and do you remember how? I reach the exact same problem, I don't found any response or tips to resolve it.
Here, a bit of me code :
void _listenToUserDatabase(String key) async {
_userStream = _usersDatabase.child(key).onValue.listen((event) {
if (event.snapshot.value != null) {
final String source = jsonEncode(event.snapshot.value);
final Map<String, dynamic> json = jsonDecode(source);
_user = UserModel.fromJson(json, key: key);
_userKey = key;
notifyListeners();
}
}, onError: (e) {
print("Listen to user database error $e");
}, onDone: () {
print("listen done");
});
}