How to get document ID for a specific document? - firebase

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)),

Related

Delete a document from firebase

I'm building an app that contains pet adoption offers. Each pet document has an ID that's generated by DateTime.now() + the user ID to make it unique, anyway, I'm trying to write a deleting method within the Slidable widget to delete the adoption offer.
The problem is that I'm unable to reach the document ID to delete it.
Is there a way to delete a document without getting the ID?
This is the Firebase database
Here is my current code
Future getOffersList() async {
List<PetTile> tiles = [];
List<Slidable> slidables = [];
var data = await FirebaseFirestore.instance
.collection('pets')
.where('owner',
isEqualTo: FirebaseAuth.instance.currentUser!.uid.toString())
.get();
_petsList = List.from(data.docs.map((doc) => Pet.fromSnapshot(doc)));
for (var pet in _petsList) {
tiles.add(PetTile(pet: pet));
}
for (var tile in tiles) {
slidables.add(
Slidable(
child: tile,
endActionPane: ActionPane(
motion: const DrawerMotion(),
children: [
SlidableAction(
onPressed: (value) async {
var ref = FirebaseFirestore.instance
.collection('pets')
.where('id', isEqualTo: tile.pet.id)
.get();
// Deleting...
},
backgroundColor: Color(0xFFFE4A49),
foregroundColor: Colors.white,
icon: Icons.delete,
label: 'Delete',
),
],
),
),
);
}
}
You can get the id of the document by doing the following steps:
Add await infront when you're accessing the conditioned data from firebase collection.. in your case in front of FirebaseFirestore.instance
*This will return a QuerySnapshot rather than a Future instance of the same.
You need to get the doc and the id of that doc.. write:
final id= ref.docs[0].id
*Using first index(0) because i am assuming that only one pet id matches with other pet id.
since you have the id now.. you can perform the delete function

How to convert serverTimestamp to String when using StreamBuilder

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>

How to store data of collection from firestore into a list?

I am looking to store fetch data from firestore into a List which would contain data from all of its documents.
I defined list as :
List retrievedData = List();
next, on press of button, I wanted to print data in all documents of a specific collection. So, I did this:
RaisedButton(
onPressed: () async {
var collectionReferece = await Firestore.instance.collection('insults');
collectionReferece.getDocuments().then((collectionSnapshot){
retrievedData = collectionSnapshot.documents.toList();
});
print(retrievedData);
},
I am expecting this in console:
I/flutter (11351): [{index: 200, title: This is a test 1},{index: 100, title: This is a test 2}]
But I get this:
I/flutter (11351): [Instance of 'DocumentSnapshot', Instance of 'DocumentSnapshot']
Also, I just want to store this data in a list or any other variable. Help me out. Thank you.
Edit:
I tried to use forEach but it keeps on adding on every press of button.
If you want to:
retrieve data from firestore
add to list
create listview.builder
Then you can do the following, first declare the following variables under your State class:
class _MyHomePageState extends State<MyHomePage> {
bool isFirstTime = false;
List<DocumentSnapshot> datas = List<DocumentSnapshot>();
Next, create a method called getData() which will be referenced in onPressed:
floatingActionButton: FloatingActionButton(
onPressed: getData,
tooltip: 'Increment',
child: Icon(Icons.add),
),
getData() async {
if (!isFirstTime) {
QuerySnapshot snap =
await Firestore.instance.collection("insults").getDocuments();
isFirstTime = true;
setState(() {
datas.addAll(snap.documents);
});
}
}
Here on press of the FAB, you will get the data inside the insults collection. We use the boolean to only retrieve once per click. Inside the method dispose which you override:
#override
void dispose() {
super.dispose();
this.isFirstTime = false;
}
}
You can assign isFirstTime to false again. Then to display the data, You can use the property body of AppBar, assign it to Center widget, and the Center widget will contain the listview:
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: ListView.builder(
itemCount: datas.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${datas[index]["index"]}'),
subtitle: Text('${datas[index]["title"]}'),
);
},
),
Using listview.builder, you will have a list in your screen and you dont have to use forEach to iterate a list. You just have to use the get operator [] to be able to get the data inside the list.
Any code that needs access to the data from Firestore, need to be inside the then. So:
var collectionReferece = await Firestore.instance.collection('insults');
collectionReferece.getDocuments().then((collectionSnapshot){
retrievedData = collectionSnapshot.documents.toList();
print(retrievedData);
});
But you'll typically want to separate the data loading out of the build() method, and use state or FutureBuilder to get the data from the database into the rendered output. Some examples of that:
Flutter/Firebase_Auth: a build function returned null for using state
How to use one field of firebase to login for another example of using state
how do i call async property in Widget build method for an example of using a FutureBuilder
i think its because the .toList() method put those 2 documents just same datatype as "DocumentSnapshot" in the List. try printing this to be sure.
print(retrievedData[0]['title']);

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);
});

Firestore replicating a SQL Join for noSQL and Flutter

I realise there is many questions in regards to replicating joins with NoSql document databases such as FireStore, however i'm unable to find a thorough solution utilising Dart/Flutter with FireStore.
I have done some research i feel that in the following example i would be looking for a 'many to many' relationship (please correct me if this is wrong) as there may be a future need to look at all profiles as well as all connections.
In firebase, i have two root level collections (profile & connection):
profile
> documentKey(Auto Generated)
> name = "John Smith"
> uid = "xyc4567"
> documentKey(Auto Generated)
> name = "Jane Doe"
> uid = "abc1234"
> documentKey(Auto Generated)
> name = "Kate Dee"
> uid = "efg8910"
connection
> documentKey(Auto Generated)
> type = "friend"
> profileuid = "abc1234"
> uid = "xyc4567"
> documentKey(Auto Generated)
> type = "family"
> profileuid = "abc1234"
> uid = "efg8910"
For this example the 'connection' documents have been created hypothetically for the user John Smith (uid: xyc4567) when he connected to Jane Doe (uid: abc1234) and Kate Dee (uid: efg8910).
Here is the relational SQL i'm looking to replicate to show a list of profiles which John Smith has connected with:
Select * FROM profile, connection
WHERE profile.uid = connection.profileuid
AND profile.uid = "xyc4567"
In flutter my flutter app i have a fireStore query starting point:
stream: Firestore.instance.collection('profile')
.where('uid', isEqualTo: "xyc4567").snapshots(),
Obviously it only returns from one collection. How do i join the collections in a many to many relationship?
Unfortunately, there is no JOIN clause in Cloud Firestore nor in others NoSQL databases. In Firestore queries are shallow. This means that they only get items from the collection that the query is run against. There is no way to get documents from two top-level collection in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use properties of documents in a single collection.
So the most simple solution I can think of is to query the database to get the uid of a user from the profile collection. Once you have that id, make another database call (inside the callback), and get the corresponding data that you need from the connection collection using the following query:
stream: Firestore.instance.collection('connection').where('uid', isEqualTo: "xyc4567").snapshots(),
Another solution would be to create a subcollection named connection under each user and add all connection objects beneath it. This practice is called denormalization and is a common practice when it comes to Firebase. If you are new to NoQSL databases, I recommend you see this video, Denormalization is normal with the Firebase Database for a better understanding. It is for Firebase realtime database but same rules apply to Cloud Firestore.
Also, when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.
Suppose, you want to use a Stream that depends on some Future objcets.
Stories
Document ID (Auto Generated) //Suppose, "zddgaXmdadfHs"
> name = "The Lion & the Warthog"
> coverImage = "https://...."
> author = "Furqan Uddin Fahad"
> publisDate = 123836249234
Favorites
Document ID (Auto Generated)
> storyDocID = "zddgaXmdadfHs" //Document ID of a story
> userId = "adZXkdfnhoa" //Document ID of a user
Sql equivalent query should look like this
SELECT * FROM Favorites AS f, Stories AS s
WHERE f.storyDocID = s.DocumentID
AND f.userId = user.userId
And Firestore query like this
final _storeInstance = Firestore.instance;
Stream <List<Favorite>> getFavorites() async* {
final user = await _getUser(); //_getUser() Returns Future<User>
yield* _storeInstance
.collection('Favorites')
.where('userId', isEqualTo: user.userId)
.snapshots()
.asyncMap((snapshot) async {
final list = snapshot.documents.map((doc) async {
final story = await _getStory(doc['storyDocID']);
return Favorite.from(doc, story); //Favorite.from(DocumentSnapshot doc, Story story) returns an instance of Favorite
}).toList(); //List<Future<Favorite>>
return await Future.wait(list); //Converts List<Future<Favorite>> to Future<List<Favorite>>
});
}
Future<Story> _getStory(String storyDocID) async {
final docRef = _storeInstance
.collection('Stories')
.document(storyDocID);
final document = await docRef.get();
final story = Story.from(document);
return story;
}
I did some like this to join results from two colections objects and categories.
i did two StreamBuilders to show in a list, in the first one i got the categories and put in a map, then i query the objects and get the category object from the map using the categoryID:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('categoryPath')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> categorySnapshot) {
//get data from categories
if (!categorySnapshot.hasData) {
return const Text('Loading...');
}
//put all categories in a map
Map<String, Category> categories = Map();
categorySnapshot.data.documents.forEach((c) {
categories[c.documentID] =
Category.fromJson(c.documentID, c.data);
});
//then from objects
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('objectsPath')
.where('day', isGreaterThanOrEqualTo: _initialDate)
.where('day', isLessThanOrEqualTo: _finalDate)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> objectsSnapshot) {
if (!objectsSnapshot.hasData)
return const Text('Loading...');
final int count =
objectsSnapshot.data.documents.length;
return Expanded(
child: Container(
child: Card(
elevation: 3,
child: ListView.builder(
padding: EdgeInsets.only(top: 0),
itemCount: count,
itemBuilder: (_, int index) {
final DocumentSnapshot document =
objectsSnapshot.data.documents[index];
Object object = Object.fromJson(
document.documentID, document.data);
return Column(
children: <Widget>[
Card(
margin: EdgeInsets.only(
left: 0, right: 0, bottom: 1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(0)),
),
elevation: 1,
child: ListTile(
onTap: () {},
title: Text(object.description,
style: TextStyle(fontSize: 20)),
//here is the magic, i get the category name using the map
of the categories and the category id from the object
subtitle: Text(
categories[object.categoryId] !=
null
? categories[
object.categoryId]
.name
: 'Uncategorized',
style: TextStyle(
color: Theme.of(context)
.primaryColor),
),
),
),
],
);
}),
),
),
);
I'm not sure if is what you want or is clear but i hope it help you.
I think denominational should not be preferred because to maintain the it you have to make extra writes to firestore
instead jorge vieira is correct since you are allowed to make double reads as compare to the writes
so its better to read twice instead of writing writing data twice and its also very impractical to remember every demoralized thing in a large project

Resources