Create and update database with input from PageView.builder in flutter - firebase

I have a PageView.builder that has a textFormfield and a title. The builder iterates through the elements of the list and once on the last one, there is a submit button that I would like to send the Key : Value pairs to firestore. when I use for each, it only creates the content of the last item of the list multiple times.
here is my create and update function:
Future updateDatabase(String remarkText) async {
return await databaseCollection.document().setData({
questionItems[index].title : questionItems[index].remarkText
});
}
and this is how I call it in my button
onPressed: () async {
questionItems.forEach((question) async {
await updateDatabase(remarkText);
});
},
How can I loop through them to send data for the previous items as well? Please help.

I think it's iterating over all items, but updating always the same item in Firestore. The variable index should be changed in each iteration somehow. Otherwise each iteration will set value on the same questionItems[index].
I hope it will help!

Related

Flutter: How can use FieldValue.arrayRemove?

How can I delete an array of array of objects from Firestore? I want to delete it inside a list.generate
Here is my database structure, I want to delete [{street A, number 25}]
List.generate(
...
IconButton(
icon: const Icon(CupertinoIcons.trash),
onPressed: () async {
try {
await FirebaseFirestore.instance
.collection('users')
.doc(data['uid'])
.update(
{
'adress':
FieldValue.arrayRemove(
??)
},
);
} catch (e) {
print(e);
}
},
),
)),
As I see in your screenshot, the adress field is not of type array, it's a Map:
So there is no way you can call FieldValue.arrayRemove. If you want to remove that field, then please use the following lines of code:
Firestore.instance.collection('users').document(data['uid']).set(
{'adress': FieldValue.delete()},
SetOptions(
merge: true,
),
)
Edit:
After you have updated the screenshot, now the adress field is indeed of type array:
If you want to delete a specific object from the array using FieldValue.delete(), you should use all the data in the object and not partial data. I have even written an article regarding updating an array of objects in Cloud Firestore:
How to update an array of objects in Firestore?
Edit2:
In code it should look like this:
Firestore.instance.collection('users').document(data['uid']).update({
'adress': FieldValue.arrayRemove(elementToDelete),
});
Please note that elementToDelete object should contain both fields populated. The number should hold the value of "25" and street should hold the value of "street A". It will not work if you use only one.
However, if you thought that Firestore provides a direct way to delete an array element by index, please note that is not possible. In such a case, you'll have to read the document, remove the element from the array, then write the document back to Firestore.

increment a value in firestore with flutter

hi i am trying to increment a value when clicked the button if data is available in firestore this is my code bloc if you have any suggestion lmk please
int q = 0;
final snapShot = await Firestore.instance.collection('cart').document("LIihBLtbfuJ8Dy640DPd").get();
if(snapShot.exists){
q=q+1;
}
Firestore.instance.runTransaction((Transaction transaction) async {
await transaction.update(
Firestore.instance
.collection("cart")
.document("LIihBLtbfuJ8Dy640DPd"),
{
foodItem.name: {
'itemName': foodItem.name,
'imgUrl': foodItem.imageAssetPath,
'itemPrice': foodItem.price,
'quantity': q,
}
});
});
In November 2021, this worked for me.
FirebaseFirestore.instance.collection('users').doc(currentUser?.id).update({
'bronzeBadges': FieldValue.increment(2),
});
var quantityref = db.collection("cart").document("LIihBLtbfuJ8Dy640DPd");
// Increment the quantity field by 1.
quantityref.update({
"quantity" : firebase.firestore.FieldValue.increment(1)});
If your want to change a value based on the previous one, you have basically two approaches:
Make use of transactions. I see you're doing that but incorrectly, because you're fetching the current value outside of it, and it could change by the moment you run the update, causing data inconsistencies. I don't know about Flutter, but as far as I know, a Transaction in Firebase consists in a read operation followed by one or more write operations, and the value returned from the read will be the very last one and won't be changed before you finish the transaction, so you can be sure you're working with the latest one. I suggest you to read the Transactions docs.
increment method (recommended): See this see this answer for incrementing in Flutter
First of all, you need to get the desired document and its elements to update the document of fields. In your example, it is quantity.
First, get the document:
Firestore.instance
.collection('cart')
.document('documentID')
.get()
.then((DocumentSnapshot ds) {
// use ds, parse ds then access the quantity
});
After doing the job, you need to update the field. Thankfully, there is an updateData function in firestore instance.
db.collection('cart')
.document('documentID')
.updateData({'quantity': someQuantity});
Hope it helps.

Is it possible to go through documents in cloud firestore to see if a value of a property is equal to a comparing one?

I have website written in plain javascript to keep daily to-do tasks and the app crashed lately because different tasks of the same date was created on accident. My question is...
how can i write an if statement that checks if a document from a collection has a property (in my case the date) that is equal to the one in the input field of my form. i guess it should check after i click submit? if it exists, creation should be denyed, if not, ok to proceed.
i am using cloud firestore by the way... many thanks in advance for the help!
First, make a query to get a document that has same date:
var query = db.collection("yourCollectionName").where("date", "==", dateInInputfield);
query.get().then(function(querySnapshot) {
if (querySnapshot.empty) {
//empty
} else {
// not empty
}
});
If empty{you can proceed}, if notEmpty{some other task already exist on same date}
If you are making an app like this, a cleaner approach will be to name the id of a document as it's date, for eg. if a task is created at timestamp of 1234567, create a document named 1234567 and inside it, store all the necessary information.
By following this approach, if you create a new task, simply fetch a document by the name in inputfield,
var docRef = db.collection("yourCollectionName").doc("date");
docRef.get().then(function(doc) {
if (doc.exists) {
//this means some other document already exists
} else {
//safe to create a new document by this date.
}
}).catch(function(error) {
console.log("Error:", error);
});

how to check if subscribe is done loading all existing rows?

I want to load all the items on start without showing any message, but once after loaded. I want to capture any new row in subscriber and show it to the desktop notification.
The problem is, I'm not sure how to check if all the previous items are loaded and if the row is new item or is it from previous existing item.
this.items = this.af.database.list('notifications/'+this.uid+'/');
this.items.subscribe(list => {
list.forEach(row => {
// doing something here...
});
// once all the rows are finished loading, then any new row, show desktop notification message
});
I have user lodash for the minimal code.
// this varible holds the initial loaded keys
let loadedKeys = [];
this.items = this.af.database.list('notifications/'+this.uid+'/');
this.items.subscribe((list)=>{
// we skip it while initial load
if(!_.isEmpty(loadedKeys)){
// different is the new keys
let newKeys = _.difference(_.map(list, "$key"), loadedKeys);
if(!_.isEmpty(newKeys)){
// ... notification code here
}
}
loadedKeys = _.map(list, "$key");
});
The behave you are looking for is the default Subject approach in RxJS.
Check this reactiveX url to follow the marble diagram of Publish Subject (the equivalent for Subject in RxJS).
So you have two easy options:
1) manually index witch rows you want to display like #bash replied
2) create a Rx.Subject() and assign only the newest's rows to it. Then you subscribe to this subject in your app workflow.
The advantage of method 2 is when a new .subscribe occur, it will not retrieve previous data.
Edit: I wrote this codepen as a guide to implement your custom RxJS Subject. Hope it helps.
Assuming your rows have something unique to match with previous rows you can do the following:
// A Row item has a unique identifier id
interface Row {
id: number;
}
this.rows: Row[];
this.items$ = this.af.database.list(`notifications/${this.uid}/`).pipe(
tap(list => {
// if rows is not array, first time...
if(!Array.isArray(this.rows)) {
// first time nothing to do
return;
}
// returns true if some item from list is not found in this.rows
const foundNewRow = list.some(item => {
return ! this.rows.find(row => row.id === item.id);
});
if (foundNewRow) {
// call method to show desktop message here
}
}
);
I used a pipe and a tap operator (that you will have to import). If you subscribe to this.items$ the tap operator will do the work:
this.items$.subscribe((items => this.rows = items));
If you do not want to set this.rows when normally subscribing than you can also do this in the tap operator. But that would assume you only use it for checking difference between existing and new items.

How to bypass unique ID and reference child nodes

My firbase database looks like this:
app
users
-gn4t9u4ut304u9g4
email
uid
How do I reference email and uid? When I try this:
$rootScope.dashtype.child('users').orderByChild('uid').equalTo($rootScope.auth.uid).on('value', function(snapshot){
$rootScope.user = snapshot.val();
console.log($rootScope.user);
})
I get the correct object, but with the unique id as root:
Object {-JvaZVrWGvJis0AYocBa: Object}
And because this is a dynamic property, I don't know how to reference the child objects. I just want to be able to access the user fields like this: $rootScope.user.email etc.
Since you're requesting a value, you get a list of users as a result. It may only be one user, but it's still a list of one.
You will have to loop over the snapshot, to get to the child node:
$rootScope.dashtype.child('users').orderByChild('uid').equalTo($rootScope.auth.uid).on('value', function(snapshot){
snapshot.forEach(function(userSnapshot) {
$rootScope.user = userSnapshot.val();
console.log($rootScope.user);
});
});
Since there's only a single user in the list, the loop for execute just once.
You are mixing regular Firebase JavaScript with AngularFire here. This means that you will need to inform AngularJS that you updated the scope, so that it will rerender the view:
$rootScope.dashtype.child('users').orderByChild('uid').equalTo($rootScope.auth.uid).on('value', function(snapshot){
snapshot.forEach(function(userSnapshot) {
$timeout(function() {
$rootScope.user = userSnapshot.val();
console.log($rootScope.user);
});
});
});

Resources