i have a problem with a Flutter application.
I want to receive data from a firebase firestore by an Document id.
The problem is, i cant replicate the data in one collection, so i have to use two collections.
My data ist a Measurement(String, String,..., List)
MeasurementData(String,String,Number)
As far as i understand the Firestore i cant make such a list in one collection, so i have created a second one in whitch each document consists of three arrays. I also save the id from the Measurement Document where it belongs to.
To use it in Flutter a use a StreamBuilder widget. For easer access i convert the document bevor using into a Dart Object. Because i have two Streams which should be converted to one Object i have a predessecor StreamMeasurement object which is basicly the same as the Measurement Object, but without the MeasurementData list, because this first needs to be created, instead it only saves a id for the measurementData Document.
After that i use the Rx.zip2 to create an Measurement Object from the StreamMeasurement Object and add the List to have a complete object.
Class Service
/// The firebase instance
FirebaseFirestore db = FirebaseFirestore.instance;
///Finds and returns a [Measurement] by its [id]
Stream<Measurement> getById(String id) {
Stream<StreamMeasurement> streamMeasurementStream =db.collection("measurements").doc(id)
.snapshots().map((document) => StreamMeasurement.fromJson(document.data()!));
return Rx.zip2(streamMeasurementStream, getMeasurementDataStreamById(id)
, (StreamMeasurement streamMeasurement, List<MeasurementData> measurementData) {
Measurement measurement = Measurement.fromStreamMeasurement(streamMeasurement, measurementData);
if(measurement == null)
{
//TODO Write a concrete exception to throw and catch
throw Exception('Measurement Stream is null');
}
return measurement;
}) ;
}
/// Returns a [Stream<List<MeasurementData>>] for a given Measurement id
Stream<List<MeasurementData>> getMeasurementDataStreamById(String id)
{
return db.collection("measurementData").where("measurement", isEqualTo: id).
snapshots().
map((querySnapshot) => querySnapshot.docChanges)
.expand((changes) => changes)
.map((change) => change.doc).map((document) => MeasurementData.fromJson(document.data()!));
}
Class DetailScreen
#override
Widget build(BuildContext context) {
MeasurementService measurementService = MeasurementService();
Stream<Measurement> measurementstream = measurementService.getById(
"GmfzveKeozkcfdlrk75s");
return StreamBuilder<Measurement>(
stream: measurementstream,
builder: (BuildContext context, AsyncSnapshot<Measurement> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return Text('Something went wrong' + snapshot.error.toString());
}
if (snapshot.connectionState == ConnectionState.waiting) {
print(snapshot);
return Text("Loading" + snapshot.connectionState.toString());
}
Measurement? measurement = snapshot.data;
return SafeArea(
Print(snapshot) => I/flutter (19335): AsyncSnapshot(ConnectionState.waiting, null, null, null)
The result is, that the StreamBuilder is stuck in ConnectionState.waiting state if i use the getById(id) function
Okay, ... Question solved. The problem was a unmatched whitespace before the id
Related
I have an app where admin can delete all documents in the firebase collection and add an x number of new documents, this works beautifully, but my streambuilder isn't updating properly,
the stream builder is getting back only one document everytime you delete all documents and create new ones, it only returns one, and like when you leave the app and come back, it fetches the proper amount of documents, all I can find online is that it's wrong to use a loop when querying and I've removed my for loop and am now using the map method, still, it is the same, here is my stream builder code
StreamBuilder<QuerySnapshot>(
stream: _store.collection("picks").snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<PickCard> pickCards = [];
final documentSnapshots = snapshot.data!.docs;
debugPrint(documentSnapshots.length.toString());
if (documentSnapshots.isNotEmpty) {
documentSnapshots.map((e) {
pickCards.add(
PickCard(
pickerPosition: e["pickerPosition"],
pickerName: e["pickerName"],
isPicked: e["isPicked"],
pickerEmail: e["pickerEmail"],
),);
}).toList();
dHelp.setCards(
context,
pickCards,
);
dHelp.setContributors(context, documentSnapshots.length);
}
} else {
}
the print document snapshot length is always 1 when they get created, but after refresh, the actual length updates, but in the firebase console, everything works perfectly, the documents update effectively,
here is a video of the problem https://www.dropbox.com/s/25qqnh0ttgemgf1/2022-08-16%2010-26-46.mp4?dl=0
I found that passing the stream directly to the streamBuilder was causing the stream to restart each time the build method rebuilt, which was supposed to be whenever the stream returns new data, so, it was kinda knotting over itself,
I instantiated the stream in the state then passed it to the streamBuilder, so now it's only created once in the lifetime of the page
// created this variable
late Stream<QuerySnapshot> _stream;
#override
initState() {
// gave it a value in iniState
_stream = _store.collection("picks").snapshots();
super.initState();
}
StreamBuilder<QuerySnapshot>(
stream: _stream, // then added this here
builder: (context, snapshot) {
if (snapshot.hasData) {
List<PickCard> pickCards = [];
final documentSnapshots = snapshot.data!.docs;
debugPrint(documentSnapshots.length.toString());
if (documentSnapshots.isNotEmpty) {
documentSnapshots.map((e) {
pickCards.add(
PickCard(
pickerPosition: e["pickerPosition"],
pickerName: e["pickerName"],
isPicked: e["isPicked"],
pickerEmail: e["pickerEmail"],
),);
}).toList();
dHelp.setCards(
context,
pickCards,
);
dHelp.setContributors(context, documentSnapshots.length);
}
} else {
}
I'm try to get sub collection from each doc ex.clothes,notifer witch I have more docs , that means I don't know its id ,My bossiness Logic was to fetch the main collection for getting all the documents and then for each doc get its sub collection and I did that with Future implementation ,but I can't do it using Stream to return the final Sub Collection SnapShots ex.properitres for listing to changes . by Future it rebuild every time and if I stop widget rebuilding by AutomaticKeepAliveClientMixin I could not get any Firesotre changes . Thanks in advance .
here is my Future implementation but again I need this implementation by Stream ^_^:
Future<List<Category>> getPropertiesDocs() async {
List<QueryDocumentSnapshot> _firstListOfDocs = [];
List<Category> _categorListOfDocs = [];
List<QueryDocumentSnapshot> _secoudListOfDocs = [];
final QuerySnapshot result = await _firebaseFirestore.collection('categories').get();
result.docs.forEach((element) {
// print(element.id);
_firstListOfDocs.add(element);
});
for (var i in _firstListOfDocs) {
final QuerySnapshot snapshot2 = await i.reference.collection("properties").get();
snapshot2.docs.forEach((element) {
_secoudListOfDocs.add(element);
_categorListOfDocs.add(Category.fromSnapShpt(element));
});
}
_firstListOfDocs.clear();
_secoudListOfDocs.clear();
return _categorListOfDocs;
}
From your future implementation,
You want to get all documents in categories collection.
For each document in categories collection, you want to get the properties subcollection.
For the first requirement, we can simply stream the categories collection.
For the second requirement, it is not advisable to stream properties collection from each categories subcollection. This won't scale well with large datasets.
Instead we will stream the collectionGroup properties. Streaming the collection group properties will fetch all collections with the name properties (no matter the location). To effectively use this, no other collection should be named properties (except the ones you want to fetch), or you rename your collection to something distinct like properties_categories.
// this streamBuilder will fetch stream for categories collection.
StreamBuilder<QuerySnapshot>(
stream: _firebaseFirestore.collection('categories').snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Delivery>> snapshot) {
if (snapshot.hasError) return Message();
if (snapshot.connectionState == ConnectionState.waiting)
return Loading();
print('categories snapshot result');
print(snapshot.data.docs.map((e) => e.data()).toList());
// _firstListOfDocs is given below (renamed to _categoryDocs)
List<QueryDocumentSnapshot> _categoryDocs = snapshot.data.docs;
// this streamBuilder will fetch all documents in all collections called properties.
return StreamBuilder<QuerySnapshot>(
stream: _firebaseFirestore.collectionGroup('properties').snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> propertiesSnapshot) {
if (propertiesSnapshot.hasError) return Message();
if (propertiesSnapshot.connectionState == ConnectionState.waiting)
return Loading();
print('properties snapshot result');
print(propertiesSnapshot.data.docs.map((e) => e.data()).toList());
// _secoudListOfDocs is given below (and renamed to _propertiesDocs)
List<QueryDocumentSnapshot> _propertiesDocs = propertiesSnapshot.data.docs;
// _categorListOfDocs is given below (and renamed to _categories)
List<Category> _categories = propertiesSnapshot.data.docs
.map((e) => Category.fromSnapShpt(e)).toList();
// return your widgets here.
return Text('Done');
},
);
},
)
If your reason for fetching categories collection data is simply to loop over it and fetch properties collection, then you can delete the first streamBuilder above since we do not need to use it to fetch properties collection.
I have a StreamBuilder that is connected to Firestore. It supplies a stream of type List<GameResults>. GameResults is a class that has a key called competitors, which is an array of Competitor objects, each of which has a key called competitorId, which is what I'd like to use to filter. When I call GameResults.fromMap in the gameStream below, I am assuming the resulting stream contains objects that I can use dot notation to access the object properties. When I try to filter the stream based on a specific value (a userId) inside the competitors array, I get an error that says: type '(dynamic) => dynamic' is not a subtype of type '(GameResults) => bool'. Can anyone help me understand where I'm going wrong?
#override
Widget build(BuildContext context) {
final Topic topic = widget.topic;
final firestore = Firestore.instance;
Stream<List<GameResults>> gameStream = firestore
.collection('gameResults')
.where('topicId', isEqualTo: topic.topicId)
.snapshots()
.map((snapshot) {
return snapshot.documents.map((doc) {
return GameResults.fromMap(doc.data);
}).toList();
});
return StreamBuilder(
stream: gameStream,
builder: (context, snapshot) {
final games = snapshot.data;
//below is the offending code:
final myGame = games
.where((game) => game.competitors
.where((competitor) => competitor.competitorId == userId))
.toList()[0];
My Firestore data is
UPDATE: After adding the return type List<GameResults> to the variable games in the streambuiler, I ended up implementing a method that ran a for loop to pull out the correct object from the stream. Below is the for loop:
for (var game in games) {
var competitors = game.competitors;
var isMyGame = competitors.contains('competitorId' == userId);
if (isMyGame != null) {
return game;
}
}
}
final game = getMyGame(games);
I am trying to find all documents in a collection the fit a certain criteria, and have that in a Streambuilder so that I can create a table using data from all the documents it returns. But my stream inside the Streambuilder keeps returning null for my query in Flutter. I try the same query in Python and it returns the right docs. Is it not possible to have a where query in a streambuilder? For reference, this is for a web app.
Here's my code
class ContainerCustomerTable extends StatefulWidget {
final company;
ContainerCustomerTable({this.company});
#override
_ContainerCustomerTableState createState() => _ContainerCustomerTableState();
}
class _ContainerTableState extends State<ContainerCustomerTable> {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection(widget.company).where('some_criteria', isEqualTo: true).snapshots(),
builder: (context, snapshot) {
print(snapshot);
QuerySnapshot docSnapshot = snapshot.data;
if (docSnapshot != null) {
return Text('test1');
} else{
return LoadingIndicator();
}
}
);
I am printing out the snapshot just to check what it returns and it gives me this
AsyncSnapshot<QuerySnapshot>(ConnectionState.active, null, [cloud_firestore/unknown] NoSuchMethodError: invalid member on null: 'includeMetadataChanges')
So it's returning null for that query, but again the same query in python produces the right results. What am I doing wrong?
And for the record, when I run the following, it works:
FirebaseFirestore.instance.collection(widget.company).doc('some_doc').snapshots()
but for some reason, when I just run the snapshot on the collection or on the where, I get the error posted above
I figured it out. My syntax was just fine, it was the firestore js package that was wrong. If you are using firestore for a web app, use this version in your web/index.html:
<script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-firestore.js"></script>
not the last version 8.0.2, when I downgraded everything worked as expected.
Note that you are also printing the snapshot and assigning the docSnapshot before it has data here:
print(snapshot);
QuerySnapshot docSnapshot = snapshot.data;
if (docSnapshot != null) {
return Text('test1');
} else{
return LoadingIndicator();
}
For safety I would go like:
if (snapshot.hasData) {
print(snapshot);
QuerySnapshot docSnapshot = snapshot.data;
return Text('test1');
} else{
return LoadingIndicator();
}
I want to return my all documents in firestore my document in Identifier based on name but when I do my function its return to me With the same number I have documents but the name its different instance of DocumentSnapshot but I need to return same names I have. How can I do this? Below is the code I am using
Widget build(BuildContext context) {
// TODO: implement build
return StreamBuilder < QuerySnapshot > (
stream: Firestore.instance.collection("Institute")
.document(widget.id).collection("Ravs").snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
print('list of docment:${snapshot.data.documents.toList()}');
};
return CircularProgressIndicator();
}
);
}
The Instance of DocumentSnapshot log is saying that you are interacting with the Snapshot object itself. which is what your code is indeed doing.
To get access to the data inside the Snapshot you have to add the .data() call next to your Snapshot, so your stream should look like this:
stream: Firestore.instance.collection("Institute").document(widget.id)
.collection("Ravs").snapshots().data()