How to convert serverTimestamp to String when using StreamBuilder - firebase

What I want
To order ListView based on server timestamp
My code
Adding to firestore collection:
onPressed: () {
FirebaseFirestore.instance.collection('things').add(
{
// some code
'timestamp': FieldValue.serverTimestamp(),
},
);
Get data from the firestore and ordered them based on the server timestamp
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('things').orderBy('timestamp').snapshots(),
Expected behavior
List View are displayed ordered based on the server timestamp
What I got
This error:
Expected a value of type 'String', but got one of type 'Timestamp'
What I've tried
I've tried adding toString() when sending the data to the firestore: 'timestamp': FieldValue.serverTimestamp().toString(), but then the data on the firestore didn't store timestamp, instead they stored FieldValue(Instance of 'FieldValueWeb').
I know that I probable have to convert them to String when I'm getting the data from the firestore, but I have no idea how to do that. I've tried adding in toString() when getting the data into the stream as such:
stream: FirebaseFirestore.instance.collection('things').orderBy('timestamp').toString().snapshots()
but then it shows below error and won't compile.
The method 'snapshots' isn't defined for the type 'String'.
The official document does not say anything about converting them to String either.
If anybody knows how to solve this please help me, I'm really stuck here.
Full StreamBuilder and ListView code
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('things').orderBy('timestamp').snapshots(),
builder: (context, snapshot) {
List<Text> putDataHere = [];
final things = snapshot.data.docs;
for (var thing in things) {
final myData = Map<String, String>.from(thing.data());
final myThing = myData['dataTitle'];
final thingWidget = Text(myThing);
putDataHere.add(thingWidget);
}
return Expanded(
child: ListView(
children: putDataHere,
),
);
},
);

You can try this:
Firestore.instance
.collection("things")
.orderBy('createdAt', descending: true or false).getDocuments()
And then you can store createdAt on your client side with Timestamp, and you can get current timestamp with Timestamp.now()

Since the expected behaviour is that ListView items are ordered based on the server timestamp, you can just sort the list after you've gotten it from Firestore.
final things = snapshot.data.docs;
things.sort((a, b) {
return (a['timestamp'] as Timestamp).compareTo(b['timestamp'] as Timestamp);
});

The problem was totally different from what I had initially thought.
I honestly forgot that when I retrieve the data from the Firestore to process it, I set my Map to Map<String, String>, it was fine before because I only had String, but now that I have Timestamp type, it doesn't work.
The answer to the question is just simply changing the Map<String, String> to Map<String, dynamic>

Related

Loading Firebase childs dont works

So I made some childs in Firebase and wanted to load them as a list view in Flutter, so I made a Scaffold and this is whats inside the Body:
FutureBuilder(
future: _Ref.get(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator.adaptive();
}
if(snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
List<Widget> _list = [];
_Ref.once().then((DataSnapshot) async {
final data = await DataSnapshot.snapshot.children.toList();
data.forEach((element) async {
final value = await element.value.toString();
print(value);
_list.add(ListTile(
title: Text(value),
trailing: Icon(
Icons.delete_outlined,
),
));
});
});
print(_list);
return ListView(
children: _list,
);
}
},
),
my output looks like this:
this is what my Firebase database looks like:
But the funny thing is that if I hotreload my app it works how it is supposed to
My educated guess is that you expected 2 lines in the output, instead of the 3 lines you get.
When the Firebase SDKs see sequential, numeric keys in a snapshot, they assume that this snapshot is an array. If there's one or a few keys missing in that array, it fill them with null values. So in your database screenshot, the Firebase SDK sees an array with indexes 1 and 2, and index 0 missing.
If you want to prevent this array coercion, don't use sequential numeric keys in your data structure. You can most easily prevent this by calling push() to generate a unique key for you, or you can prefix the existing values with a short non-numeric string, e.g. "key1", "key2".
I recommend checking out this vintage blog post: Best Practices: Arrays in Firebase on why using such array-like keys is typically an antipattern in Firebase.

How to get document ID for a specific document?

I would like to get the document id of a specific document when I put its name in a function.
Here is my function:
void _getId(town) {
var data = FirebaseFirestore.instance
.collection('towns')
.where('town_name', isEqualTo: town)
.snapshots();
print(data.id);}
Here is my button:
ElevatedButton(
onPressed: () => _getId(_townChosen), child: Text('Get ID')),
The value _townChosen is a string which corresponds to the field town_name in the database. In the complete program, I get the value from a dropdown button, this part works well.
Here is the database
All documents have an town_name field.
I need the id of the chosen town and send it in others widgets to use its subcollection. Please can you help me to get the id?
First, create a variable called townid, and change your function to async, and use a stateful widget to update it, and use get instead of snapshots:
String townId = 'Get ID';
void _getId(town) async {
var data = await FirebaseFirestore.instance
.collection('towns')
.where('town_name', isEqualTo: town)
.get();
setState(() {
townId = data.docs[0].id; //because the query returns a list of docs, even if the result is 1 document. You need to access it using index[0].
});
print(townId);
}
In your button:
ElevatedButton(onPressed: () => _getId(_townChosen), child: Text(townId)),

Flutter & Firebase : How do I get a specific field from a specific document into a stream?

I have been trying to get a specific field from a specific document into a stream. But I always got null. How do I fix this?
Function
Stream<List<String>> getError() {
DocumentReference errorReference =
appSettingCollection.document('report_error');
final Stream<DocumentSnapshot> snapshots = errorReference.snapshots();
return snapshots.map((doc) {
//print(doc.data['types']);
return doc.data['types'];
});
}
Main
return StreamBuilder<List<String>>(
stream: User_DatabaseService().getError(),
builder: (context, snapshot) {
final List<String> errorTypes = snapshot.data;
print('In Error Report : $errorTypes'); // I got null??? But why?
///--------
}}
I think you have taken wrong stream.
Try to correct Stream
StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('CollectionName')
.document("DocumentID")
.get(),
And use it below for accessing specific field as:
document["FieldName"]
For accessing single field you will need to access whole document.I will suggest to use FutureBuilder follow the link below and you will get your answer more precise.
https://firebase.flutter.dev/docs/firestore/usage/

Check a specific field in a firebase document and return the document ID if field is found?

I want to return the document id if the field in the document matches what I need. I have tried this so far.
final result = await queueCollection.where('patientID', isEqualTo: uid).getDocuments();
print(result.documents);
return result;
If you want to get the elements from the database that correspond to your result query, then you should use a stream, like in the following lines of code:
Widget _build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: queueCollection.where('patientID', isEqualTo: uid).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return LinearProgressIndicator();
}
var snapshots = snapshot.data.documents;
//Do what you need to do with the data
//return a ListView for example
},
);
}
Please note that snapshots is an object of type List<DocumentSnapshot>. Now simply iterate over the list and get each DocumentSnapshot element out of it. Once you have it, simply get the patientID like this:
var patientID = userDocument["patientID"]
You should consider which method returns what in Firebase platform. getDocument() returns multiple documents if there are any. Your result variable holds QuerySnapshot now. You can iterate over every document with this code.
result.documents.forEach((DocumentSnapshot documentSnapshot ){
print(documentSnapshot.data);
});

How to get the number of Firestore documents in flutter?

I am building a flutter app and using Cloud Firestore. I want to get the number of documents in the database.
I tried
Firestore.instance.collection('products').toString().length
but it didn't work.
Firebase doesn't officially give any function to retrieve the number of documents in a collection, instead you can get all the documents of the collection and get the length of it..
There are two ways:
1)
final int documents = await Firestore.instance.collection('products').snapshots().length;
This returns a value of int. But, if you don't use await, it returns a Future.
2)
final QuerySnapshot qSnap = await Firestore.instance.collection('products').getDocuments();
final int documents = qSnap.documents.length;
This returns a value of int.
However, these both methods gets all the documents in the collection and counts it.
Thank you
With Cloud Firebase 2.0, there is a new way to count documents in a collection. According to reference notes, the count does not count as a read per document but a metaData request:
"[AggregateQuery] represents the data at a particular location for retrieving metadata without retrieving the actual documents."
Example:
final CollectionReference<Map<String, dynamic>> productList = FirebaseFirestore.instance.collection('products');
Future<int> countProducts() async {
AggregateQuerySnapshot query = await productList.count().get();
debugPrint('The number of products: ${query.count}');
return query.count;
}
It Should Be - Firestore.instance.collection('products').snapshots().length.toString();
Since you are waiting on a future, this must be place within an async function
QuerySnapshot productCollection = await
Firestore.instance.collection('products').get();
int productCount = productCollection.size();
Amount of documents in the collection
Future<int> getCount() async {
int count = await FirebaseFirestore.instance
.collection('collection')
.get()
.then((value) => value.size);
return count;
}
Instead of getting all the documents using get() or snapshots() and counting them, we can use Firebase Aggregation Queries. This will provide you with the count.
Here is an example that works in Flutter :
final collection = FirebaseFirestore.instance.collection("products");
final query = collection.where("status", isEqualTo: "active");
final countQuery = query.count();
final AggregateQuerySnapshot snapshot = await countQuery.get();
debugPrint("Count: ${snapshot.count}");
You can find more details here : https://firebase.google.com/docs/firestore/query-data/aggregation-queries
First, you have to get all the documents from that collection, then you can get the length of all the documents by document List. The below could should get the job done.
Firestore.instance.collection('products').getDocuments.then((myDocuments){
print("${myDocuments.documents.length}");
});
Future getCount({String id}) async => FirebaseFirestore.instance
.collection(collection) //your collectionref
.where('deleted', isEqualTo: false)
.get()
.then((value) {
var count = 0;
count = value.docs.length;
return count;
});
this is in dart language...
Above suggestions will cause the client to download all of the documents in the collection to get the count. As a workaround, if your write operations are not frequently happening, you can put the length of the documents to firebase remote config and change it whenever you add/delete documents from the Firestore collection. Then you can fetch the length from firebase remote configs when you need it.
Most Simplest Way :
int count = await FirebaseFirestore.instance.collection('Collection_Name').get().then((value) => value.size);
print(count);
You want to fetch data from firebase so the call will return a Future.
In order to get the int value you have to use the StreamBuilder widget or FutureBuilder.
For example:
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection("<collection name>").getStream().snapshot(),
builder: (context, snapshot) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Stack(children: [
Text(
"${snapshot.data!.docs.length}"
)
]));
});
}
Firestore.instance
.collection("products")
.get()
.then((QuerySnapshot querySnapshot) {
print(querySnapshot.docs.length);
});
#2022 Using the recent version of FirebaseFirestore you can now get the count without retrieving the entire collection.
CollectionReference ref = FirebaseFirestore.instance.collection('collection');
int count = (await ref.count().get()).count;

Resources