Can not retrieve documents from Firebase - firebase

So I'm trying to retrieve all documents from a certain collection 'avtvity' in my Firestore through loadmap() method. I have put the function in my initState() since I want to immediately add the data to my Place which is a class i created to add places. However, the data is not being placed in my 'places' which is a List which makes me question that the loadMap() is not being called.
any idea how to fix it?
here is the code:
class PlaceTrackerApp extends StatefulWidget {
final FirebaseUser firebaseUser;
PlaceTrackerApp({this.firebaseUser});
#override
_PlaceTrackerAppState createState() => _PlaceTrackerAppState();
}
class _PlaceTrackerAppState extends State<PlaceTrackerApp> {
AppState appState = AppState();
#override
void initState() {
super.initState();
print(widget.firebaseUser);
loadMap();
}
static StreamBuilder<QuerySnapshot> loadMap() {
return StreamBuilder(
stream: Firestore.instance.collection('avtivity').snapshots(),
builder: (context, snapshot) {
if(!snapshot.hasData) return Text("Loading... Please wait,");
for (int i = 0; i < snapshot.data.documents.length; i++) {
StubData.places.add(new Place(
id: snapshot.data.documents[i]['id'],
latLng: snapshot.data.documents[i]['latlng'],
name: snapshot.data.documents[i]['title'],
description: snapshot.data.documents[i]['description'],
starRating: snapshot.data.documents[i]['rating'],
category: PlaceCategory.activities,
));
}}
);
}
Here is a screenshot of database:

Related

show field from realtime database flutter

I'm still learning flutter and ,I want to get a value from Realtime database in firebase and then show it in the screen.
this is the full code , i can see the value in the terminal but when i dont know how to display it on the screen
class Body extends StatefulWidget {
#override
BodyState createState() => BodyState();
}
class BodyState extends State<Body> {
final db = FirebaseFirestore.instance;
late final reference = FirebaseDatabase.instance.ref();
late DatabaseReference databaseReference ;
#override
Widget build(BuildContext context) {
DatabaseReference tempvaleur =
FirebaseDatabase.instance.ref('temperature/esofostemperature0/valeur');
tempvaleur.onValue.listen((DatabaseEvent event) async {
print(event.snapshot.value);
final data = event.snapshot.value ;
}
);
return Column(
);
}
}
You should instead use the StreamBuilder which allows you to consume the real-time stream from a Firebase collection or document.
In your case, your build method would've looked like this:
#override
Widget build(BuildContext context) {
DatabaseReference tempvaleur =
FirebaseDatabase.instance.ref('temperature/esofostemperature0/valeur');
return StreamBuilder(
stream: tempvaleur.onValue,
builder: (context, snapshot) {
if (snapshot.hasData) {
// consume your data here
var data = (snapshot.data! as DatabaseEvent).snapshot.value;
// hoping your value is a string, but just in case...
return Text(data.toString());
}
return CircularProgressIndicator();
}
);

Flutter Firebase create a dynamic list of streams

How i can create a dynamic list of stream?
I need a function that build a stream list and return the stream.
My fuction (try to return a value like this List<Stream<'dynamic'>>:
List<Stream<dynamic>> _buildStreamList() async* {
var chat_overview_object = await query_chat_overview_id();
List<Stream<dynamic>> stream_list = [];
for (var i = 0; i < chat_overview_object.length; i++) {
stream_list.add(
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1234')
.snapshots(),
);
}
yield stream_list;
}
Why i need this?
I try to build a streambuilder with multiple stream on documents.
The streams can be different (for example one stream look at collection a document a other on collection b document b).
The example work but only with fix streams. Now i would like implement a **function which return a dynamic list of streams.
Here my code:
var _data = [];
typedef MultiStreamWidgetBuilder<T> = Widget Function(BuildContext context);
// A widget that basically re-calls its builder whenever any of the streams
// has an event.
class MultiStreamBuilder extends StatefulWidget {
const MultiStreamBuilder({
required this.streams,
required this.builder,
Key? key,
}) : super(key: key);
final List<Stream<dynamic>> streams;
final MultiStreamWidgetBuilder builder;
Widget build(BuildContext context) => builder(context);
#override
State<MultiStreamBuilder> createState() => _MultiStreamBuilderState();
}
class _MultiStreamBuilderState extends State<MultiStreamBuilder> {
final List<StreamSubscription<dynamic>> _subscriptions = [];
#override
void initState() {
super.initState();
_subscribe();
}
#override
void didUpdateWidget(MultiStreamBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.streams != widget.streams) {
// Unsubscribe from all the removed streams and subscribe to all the added ones.
// Just unsubscribe all and then resubscribe. In theory we could only
// unsubscribe from the removed streams and subscribe from the added streams
// but then we'd have to keep the set of streams we're subscribed to too.
// This should happen infrequently enough that I don't think it matters.
_unsubscribe();
_subscribe();
}
}
#override
Widget build(BuildContext context) => widget.build(context);
#override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
for (final s in widget.streams) {
final subscription = s.listen(
(dynamic data) {
setState(() {
_data.add(data);
print('data: ' + _data.toString());
});
},
onError: (Object error, StackTrace stackTrace) {
setState(() {});
},
onDone: () {
setState(() {});
},
);
_subscriptions.add(subscription);
}
}
void _unsubscribe() {
for (final s in _subscriptions) {
s.cancel();
}
_subscriptions.clear();
}
}
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiStreamBuilder(
streams: _buildStreamList(),
[
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1233')
.snapshots(),
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1234')
.snapshots(),
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1232')
.snapshots()
],
builder: _buildMain,
);
}
}
Widget _buildMain(BuildContext context) {
return MaterialApp(
home: Row(
children: [
Text(
'_data: ' + _data.toString(),
style: TextStyle(fontSize: 15),
)
],
));
}
Edit!
After a while i got a solution:
//multi stream widget builder
typedef MultiStreamWidgetBuilder<T> = Widget Function(BuildContext context);
// widget that basically re-calls its builder whenever any of the streams has an event.
class MultiStreamBuilder extends StatefulWidget {
const MultiStreamBuilder({
required this.streams,
required this.builder,
Key? key,
}) : super(key: key);
final List<Stream<dynamic>> streams;
final MultiStreamWidgetBuilder builder;
Widget build(BuildContext context) => builder(context);
#override
State<MultiStreamBuilder> createState() => _MultiStreamBuilderState();
}
//multi streambuilder
class _MultiStreamBuilderState extends State<MultiStreamBuilder> {
final List<StreamSubscription<dynamic>> _subscriptions = [];
#override
void initState() {
super.initState();
_subscribe();
}
#override
void didUpdateWidget(MultiStreamBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.streams != widget.streams) {
// Unsubscribe from all the removed streams and subscribe to all the added ones.
// Just unsubscribe all and then resubscribe. In theory we could only
// unsubscribe from the removed streams and subscribe from the added streams
// but then we'd have to keep the set of streams we're subscribed to too.
// This should happen infrequently enough that I don't think it matters.
_unsubscribe();
_subscribe();
}
}
#override
Widget build(BuildContext context) => widget.build(context);
#override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
for (final s in widget.streams) {
final subscription = s.listen(
(dynamic data) {
setState(() {
//check object contain id
var object = chat_overview_object.singleWhere(
(element) => element.chat_overview_id[0].trim() == data.id);
//add to object data
object.last_message_send = data['last_message_send'];
object.last_update = data['last_update'];
object.members_id = data['members_id'];
object.new_message = data['new_message'];
object.user_display_name = data['user_display_name'];
});
//set index
index++;
//check all data load
if (index == chat_overview_object.length) {
//set data has load
data_load.value = false;
}
},
onError: (Object error, StackTrace stackTrace) {
setState(() {});
},
onDone: () {
setState(() {});
},
);
_subscriptions.add(subscription);
}
}
void _unsubscribe() {
for (final s in _subscriptions) {
s.cancel();
}
_subscriptions.clear();
}
}
You are using a wrong approach: You should not build a separate stream for each document, then decide on which stream you are using. Instead, you should build only 1 stream that returns all those documents you may need.
Then, inside your StreamBuilder, you can apply your if condition to decide which document you need to use. This way, the document will always be up-to-date.
Make sure you apply the right where clause to call the appropriate documents from the database.
EDIT 1:
So what you need is known as a collectionGroup:
db.collectionGroup('data').where('yourField', isEqualTo: 'whatever').snapshots()

Flutter Streambuilder stream inconsistent FirebaseFirestore snapshot data

Retrieving shopping cart items my snapshot data is inconsistent. When one item is in cart I get this correctly formatted result:
{1111111111111: 1, PriceSmart: 540.0}
When two items in cart, and second item is also "PriceSmart", I get an error because returns this result:
{1111111111111: 1, PriceSmart: 300.0, 5555555555555: 1}
and should be:
{1111111111111: 1, PriceSmart: 540.0, 5555555555555: 1, PriceSmart: 300.0}
This is my firebase data structure:
First cart item:
Second cart item:
Basically is combining the "seller" (PriceSmart), when I need to return complete data from each cart item, otherwise I get an error as soon as I have more than one item in cart and seller is the same.
Please check the Stream in my code and see what is wrong with this implementation:
class PriceUpdaterWidget extends StatefulWidget {
const PriceUpdaterWidget({
Key? key,
required this.loginService,
required this.code,
required this.itemSubCategory,
}) : super(key: key);
final LoginService loginService;
final String? code;
final SubCategory? itemSubCategory;
_PriceUpdaterWidgetState createState() => _PriceUpdaterWidgetState();
}
class _PriceUpdaterWidgetState extends State<PriceUpdaterWidget> {
#override
Widget build(BuildContext context) {
CategorySelectionService catSelection =
Provider.of<CategorySelectionService>(context, listen: false);
Stream<DocumentSnapshot> priceDocStream = FirebaseFirestore.instance
.collection('shoppers')
.doc(widget.loginService.loggedInUserModel!.uid)
.collection("cartItems")
.doc(widget.code)
.snapshots();
return StreamBuilder<DocumentSnapshot>(
stream: priceDocStream,
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
SellerNameService isSellerName =
Provider.of<SellerNameService>(context, listen: false);
var sellerName = isSellerName.isSellerName;
if (snapshot.data != null) {
return Text(
snapshot.data![sellerName].toStringAsFixed(2),
textAlign: TextAlign.center,
);
} else {
return Text('No Data');
}
});
}
}
Without seeing more of your code, it's hard to make an example to exactly fit your specification, and the error you're getting doesn't match the code you posted, but, broadly, you need to:
move the Stream outside your build function and into initState
process each snapshot one at a time
stay away from processing the data in your build widget
class PriceUpdaterWidget extends StatefulWidget {
final String login, code;
const PriceUpdaterWidget(this.login, this.code);
#override
_PriceUpdaterWidgetState createState() => _PriceUpdaterWidgetState ();
}
class _PriceUpdaterWidgetState extends State<PriceUpdaterWidget> {
Stream<DocumentSnapshot> priceStream; // only one stream per widget
#override
void initState() {
super.initState();
priceStream = FirebaseFirestore.instance // set the stream once
.collection("shoppers")
.doc(widget.login)
.collection("cartItems")
.doc(widget.code)
.snapshots();
}
#override
Widget build(BuildContext context) => StreamBuilder<DocumentSnapshot>(
stream: priceStream,
builder: (context, snapshot) {
const String sellerName = "PriceSmart";
return snapshot.data == null
? const Text("No data")
: Text(
snapshot.data[sellerName].toStringAsFixed(2),
textAlign: TextAlign.center,
);
}
);
}

Firestore one-time read using Flutter, i got to printing document data in console want to output it in UI

In my Firestore DB, inside 'location' collection i have 2 docs,(named as Europe,Australia) having a field 'name' with their string values (same as their document names).
I have worked with StreamBuilder and Streams before, but this time i dont want real-time calls, but just once.
I wanna print that 'name' field data of all the docs inside location collection.
This is what my UI code looks like:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double spaceInBetween = 25;
#override
Widget build(BuildContext context) {
DatabaseService().getData();
return Scaffold(
body: Container(
child: Text("data here")
);
}
I wanna print all that documents data, with all their names using ListView.builder() on the HomePage.
This is my DatabaseService class (using the official FlutterFire Docs https://firebase.flutter.dev/docs/firestore/usage/ but didnt find what i was looking for)
class DatabaseService {
final locationCollection = FirebaseFirestore.instance.collection("location");
getData() async {
await locationCollection.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
print(doc['name']);
});
});
}
}
Also wanted to know if there's any other way i could do this, using FutureBuilder or anything else, just wanna get field data from all docs in a collection from Firestore and print it (im still learning).
Thank you :)
I think the answer is FutureBuilder. You can create a Future method which is going to get datas from Firebase servers and return it. After that you just create a FutureBuilder which is going to help you to show datas and if something wrong with the server or the internet connection you will not get any error messages because FutureBuilder will show an CircularProgressIndicator.
I made a demo code for you to demostrate FutureBuilder.
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final locationCollection = FirebaseFirestore.instance.collection("location");
#override
void initState() {
super.initState();
}
Future<List<String>> getData() async {
List<String> name = [];
await locationCollection.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
name = doc['name'];
});
});
return name;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FutureBuilder<List<String>>(
future: getData(), // call getData method
builder: (context, snapshot) {
List<String> nameList = snapshot.data ?? []; // create a local variable which is storing data from the getData method
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListView.builder( // if getData method give datas listviewbuilder is going to show datas
itemCount: nameList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(nameList[index]),
);
},
)
: Center(child: CircularProgressIndicator()); // if something wrong with the server or with the internet you will see a CircularProgressIndicator
}),
),
),
);
}
}
In order to ensure you only get the data once, you can use a FutureBuilder and ensure you define the future outside the build method (for example in the initState) so that it doesn't get called again whenever the build method is called.
FutureBuilder
...
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateWidget, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Update the getData method of your DatabaseService class to this below:
Future<List<String>> getData() async {
final QuerySnapshot locationDataSnapshot = await locationCollection.get();
final List<String> listOfNames = locationDataSnapshot.docs
.map((QueryDocumentSnapshot documentSnapshot) =>
documentSnapshot.data()['name'] as String)
.toList();
return listOfNames;
}
This code above fetches the list of documents from the location collection and maps them to a list of names, which is then returned.
You can then get define the future object to get this data in your initState and use it in your FutureBuilder like shown below:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _nameListFuture;
#override
void initState() {
super.initState();
_nameListFuture = DatabaseService().getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<String>>(
future: _nameListFuture,
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
}
final List<String> nameList = snapshot.data;
return ListView.builder(
itemCount: nameList.length,
itemBuilder: (context, index) => Text(nameList[index]),
);
},
),
);
}
}

How do I execute FutureBuilder only once in a list that gets reinitialized?

I have created a Flutter project that has a home page with a bottom navigation bar. I used an IndexedStack as the body.
I'm trying to make my CustomList() a feed which shows the most recent documents.
I intend to use pagination too.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final widgetOptions = [
CustomList(),
Page2(),
Page3(),
Page4(),
];
int _selectedItemPosition = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
//
currentIndex: _selectedItemPosition,
onPositionChanged: (index) => setState(() {
_selectedItemPosition = index;
}),
items: [
BottomNavigationBarItem(),
BottomNavigationBarItem(),
BottomNavigationBarItem(),
BottomNavigationBarItem()
],
),
body: IndexedStack(
index: _selectedItemPosition,
children: widgetOptions,
),
);
}
}
This is the code of my CustomList():
class CustomList extends StatefulWidget {
#override
_CustomListState createState() => _CustomListState();
}
class _CustomListState extends State<CustomList> {
#override
Widget build(BuildContext context) {
Future<Object> getData()
{
//get Data from server
}
return FutureBuilder<Object>(
future: getData(),
builder: (context, snapshot) {
if(snapshot.data != null)
{
if(snapshot.hasData)
{
//get Documents
}
return ListView.builder(
//
itemBuilder: (context , index) {
//return a widget that uses the data received from the snapshot
},
);
}
}
);
}
}
The issue is that every time I change the page using the bottom navigation bar, whenever I come back to my default page with the CustomList(), the FutureBuilder is fired again resulting in my list having duplicates. This is due to the CustomList() being initialized again.
How do I structure my code so that the FutureBuilder is executed only once and isn't fired repeatedly when I use the BottomNavigationBar to change the page?
This is because you get a new future every time build is called, because you pass a function call to the FutureBuilder and not a reference that stays the same.
There are several easy options to solve this.
You can store a reference to the future and pass this reference to the FutureBuilder
You can use an AsyncMemoizer from the async package to only run the future once https://api.flutter.dev/flutter/package-async_async/AsyncMemoizer-class.html
You can use the FutureProvider from the provider package https://pub.dev/documentation/provider/latest/provider/FutureProvider-class.html

Resources