Flutter firestore update subcollection - flutter

I have a collection recipes with a subcollection notes.
How can I update a note? I can't find a way to get the right document id to make the update function.
I'm passing the document to a card widget with Get.arguments and there I have a button to update the note.
The button code:
onTap: () async {
await _updateNotes(
_notesController.text,
documentSnapshot.reference.id,
);
}),
The method code:
Future<void> _updateBrewNotes(String brewNotes) async {
await Globals.updateNotes(brewNotes, documentSnapshot.reference.id,
documentSnapshot.reference.id);
Get.to(() => const HomePage());
}
That calls other file named Globals with the code:
static updateNotes(startNotes, String idRecipe, String idNotes) {
recipes
.doc(idRecipe)
.collection('notes')
.doc(idNotes)
.update({'notesStart': startNotes});
}
That recipes is a simple collection to shorten the code
static final CollectionReference recipes =
FirebaseFirestore.instance.collection('recipe');

Related

Is it possible to prevent two people from adding products to the cart at the same time with Firebase?

I am importing a product catalog using Firebase DB with Flutter. Only 1 person can add the product to the cart. If 2 or more people want to add the product to the basket at the same time, the product is added to all of them. What can I do to get this back?
void addBasket(Map<String, dynamic> data, index) async {
var _storage = await GetStorage();
final CollectionReference user_basket =
await FirebaseFirestore.instance.collection('user basket');
final CollectionReference product =
await FirebaseFirestore.instance.collection('product');
final DocumentReference ref = await product.doc(data['id']);
await FirebaseFirestore.instance.runTransaction((Transaction tx) async {
DocumentSnapshot snap = await tx.get(ref);
if (snap.exists) {
await product.doc(data['id']).delete();
await tx.update(ref, data);
await user_basket
.doc(_storage.read('uid'))
.collection('basket')
.doc(data['id'])
.set(data);
inArea.removeAt(index);
Get.back();
Get.back();
} else {
await user_basket
.doc(_storage.read('uid'))
.collection('basket')
.doc(data['id'])
.delete();
Get.back();
Get.defaultDialog(
title: "Wrong",
middleText:
"This product is being reviewed by another person.");
}
});
}
I tried to use Transaction as you can see in the code. I tested this code the first time I wrote it. when two people pressed the button at the same time, one of the 2 could add the product to the cart. I wanted to test again today. I pressed the add product to cart button, as a result both the If and Else blogs worked. So I started getting errors. The exact result I want to get is when more than 1 person wants to add the same product to the cart, to get the opposite. Let the first click on the button add the product to the basket, and the others will be informed through the Alerd Dialog.
I believe you are creating a race condition between clients. In all of my implementations of Firestore, I do not use await on the collection or query references. You should only need to await the get() method itself.
When you look at Flutter Firestore documentation for their implementation of transactions, you'll see they do not await the collection reference at the top.
https://firebase.google.com/docs/firestore/manage-data/transactions#dart
Instead of:
final CollectionReference user_basket =
await FirebaseFirestore.instance.collection('user basket');
final CollectionReference product =
await FirebaseFirestore.instance.collection('product');
final DocumentReference ref = await product.doc(data['id']);
Try using:
final CollectionReference user_basket = FirebaseFirestore.instance.collection('user basket');
final CollectionReference product = FirebaseFirestore.instance.collection('product');
final DocumentReference ref = product.doc(data['id']);
I hope that helps! I will try and put together a Firestore transactions demo to see if I can replicate and resolve that issue you described.
It could be data race issue. you can use transaction class to prevent it.

Losing data when user terminates app then runs it again

I use firebase in almost all my app and after a doc is added I add it to a variable to structure the data and organize it but however when app is terminated then ran again the variable resets although firebase docs are added to it in the first place.
How can I fix that?
An example clarifying what I mean:
//add to firebase
final doc = <String, dynamic>{
'docType':'firebase'
};
await collection.add(doc);
//add to a model's variable
context.read<Model>().addDoc(doc['docType']);
You can listen to the stream of your snapshots, so when ever data is received you can add it to your model
//You can add that in initState for example
subscription = CloudFirestoreManager.readData.listen(
(data) async {
//Add received data to model
},
);
class CloudFirestoreManager {
static Stream<QuerySnapshot<Map<String, dynamic>>> get snapshots {
return firestoreInstance
.collection('my-id')
.snapshots();
}
static FirebaseFirestore get firestoreInstance {
return FirebaseFirestore.instance;
}
static Stream<List<Map<String, dynamic>>> get readData => snapshots.map(
(snapshot) => snapshot.docs
.map(
(doc) => doc.data(),
)
.toList(),
);
}

Getting all documents uids from firestore and add them to a list .flutter

I created a new collection named users on my firestore project. Im trying to get a list of all auto generated id's in that collection. So far I tried
late List<String> userID = [];
Future getID() async {
await FirebaseFirestore.instance.collection('users').get().then(
(snapshot) => snapshot.docs.forEach((document) {
userID.add(document.reference.id);
}),
);
}
But whenever I try to access the Strings of id's in the list , it returns an empty list
getID is a future method. It will take some to fetch data. After getting data you need to call setState to update the UI. You dont need to await and .then same time
try
Future getID() async {
FirebaseFirestore.instance.collection('users').get().then((snapshot) {
snapshot.docs.forEach((document) {
userID.add(document.reference.id);
});
setState(() {});
});
}
It would be great to use FutureBuilder for future method.

Added data is only showing after reloading in flutter

here is a popup screen to add the transaction to the app, as you can see here
and when the add button pressed the data will add to database and also to the dislpay , here is the code
ElevatedButton(
//on pressed
onPressed: () async {
final _categoryName = _nameEditingController.text;
if (_categoryName.isEmpty) {
return;
}
final _type = selectedCategoryNotifier.value;
//sending the data to model class
final _category = CategoryModel(
id: DateTime.fromMillisecondsSinceEpoch.toString(),
name: _categoryName,
type: _type,
);
//inserting the data to database
await CategoryDb.instance.insertCategory(_category);
//refreshing the ui
await CategoryDb.instance.refreshUI();
//and quitting the popup screen
Navigator.of(ctx).pop();
},
child: const Text('Add'),
),
and in this code you can see that I called 2 functions that for insert data and also refresh the UI, in the refresh UI function I added the function that to get all data from database to screen, here the code of all functions for CRUD operatins
const databaseName = 'category-database';
abstract class CategoryDbFunctions {
Future<List<CategoryModel>> getCategories();
Future<void> insertCategory(CategoryModel value);
}
//CRUD operations code
class CategoryDb implements CategoryDbFunctions {
CategoryDb._internal();
static CategoryDb instance = CategoryDb._internal();
factory CategoryDb() {
return instance;
}
ValueNotifier<List<CategoryModel>> incomeCategoryListListener =
ValueNotifier([]);
ValueNotifier<List<CategoryModel>> expenseCategoryListListener =
ValueNotifier([]);
#override
Future<void> insertCategory(CategoryModel value) async {
final _categoryDB = await Hive.openBox<CategoryModel>(databaseName);
await _categoryDB.add(value);
await refreshUI();
}
#override
Future<List<CategoryModel>> getCategories() async {
final _categoryDB = await Hive.openBox<CategoryModel>(databaseName);
return _categoryDB.values.toList();
}
Future<void> refreshUI() async {
final _allCategories = await getCategories();
incomeCategoryListListener.value.clear();
expenseCategoryListListener.value.clear();
await Future.forEach(
_allCategories,
(CategoryModel category) {
if (category.type == CategoryType.income) {
incomeCategoryListListener.value.add(category);
} else {
expenseCategoryListListener.value.add(category);
}
},
);
}
}
so I checked the all things , but I couldn't find where I'm missing parts,
and here is the main part, it is adding to the database also displaying after I refresh the UI or change the tab here you can see what I mean by 'changing the tab'
this is the problem I'm trying to fix this for 2 day, i couldn't find any solution or mistake in my code
There many ways you can handle this problem.
but I dont see where you notify youre ui that the data has been changed, flutter does only update the ui when you use setState etc.. these functions help flutter updating the ui where the data changed.
i would recommend you to use setState in the place you invoke youre dialog.
onTap:(){
setState(){
await dialogStuff();
}
}

How to return a List, after a Method fills it, Flutter

I'm stuck with a problem and I wondered if you can help me.
I have a functions (in Flutter) that returns a List of Items. Now this List of Items should be Filled by an other function, which goes thought my Database and collect the right items. My Problem is, that my Function runs after the Return Statement... Here is some Code:
Future<List<MaterialItem>> getItems(String path, String fach) async {
// This is a empty List that I want to fill
List<MaterialItem> list = [];
// That's my Function, that fills the List
var result = await _db
.collection("$path/$fach/")
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
// Here the List gets filled
list.add(MaterialItem.fromSnapshot(doc.data() as Map<String, dynamic>));
});
});
// Here the List should be returned, but after my Function fills it.
return list;
}
Hope you know what my problem is, and someone can help me.
I think you could solve this using a Completer. Your function should return the Future property of the Completer and the database call should then complete it.
Take a look at the API and the example:
https://api.dart.dev/stable/2.12.4/dart-async/Completer-class.html
For example: (pseudo code)
Future<List<MaterialItem>> getItems(String path, String fach) async {
// declare a completer
Completer<List<MaterialItem>> completer = Completer();
List<MaterialItem> list = [];
final result = await _db
.collection("$path/$fach/")
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
list.add(MaterialItem.fromSnapshot(doc.data() as Map<String, dynamic>));
});
// let the database call complete the completer
completer.complete(list);
});
// return the future value of the completer
return completer.future;
}