Fairly new to Flutter and FireStore, I'm having trouble finding examples or explanations on how to add Maps to an Array (specifically, Creating the initial Array with a Map, then Reading then Adding more Maps to the collection of the Notes Array).
I have an Event Class for scheduling events and that is working very well. What I'm trying to add is a "Notes" field that would allow an some Administrator to add Notes on one day then come back and add to that Event another set of notes as things change. I don't want to "overwrite" the previous Notes, just keep adding more Notes to the Array of notes.
Specific questions:
How do I Create the "Array" when adding the entry into FireStore using Flutter?
How do I Read the data in the "Array" when it's coming back as a set of "Map fields" in Flutter?
How do I just Add to the Array with a new Note? (I think this needs FieldValue.arrayUnion([someMap]) but I'm not certain as this appears to avoid any overwriting.
The page section here shows writing it out but little else: https://firebase.google.com/docs/firestore/manage-data/add-data#data_types
Below is an example of the FireStore structure I'm trying to create.
Has anyone done this before and do you have some direction you can provide? The Firebase documentation in this space is thin...
To create an new document with array in firestore:
FirebaseFirestore.instance.collection('path/to/collection').add(
{ 'notesArray': [
{'date': 'sometime', 'notes': 'somenotes', 'user': 'someuser'}
]},
SetOptions(merge: true),
);
// or
FirebaseFirestore.instance.collection('path/to/collection').add(
{ 'notesArray': []}, // for an empty array
SetOptions(merge: true),
);
To read the data:
FirebaseFirestore.instance.doc('company').get().then((value) {
final doc = value.data()!;
print(doc['lname']); // should print Gethsemane
print(doc['notesArray'] as List); // should print notes array
final notes = doc['notesArray'] as List;
for (final note in notes) {
print(note['date']);
print(note['notes']);
print(note['user']);
}
// or
for (int i = 0; i < notes.length; i++) {
print(notes[i]['date']);
}
});
Add new data to notes array. Simply use FieldValue.arrayUnion.
E.g
FirebaseFirestore.instance.doc('path/to/doc').set(
{
'notesArray': FieldValue.arrayUnion([
{'date': 'sometime', 'notes': 'somenotes', 'user': 'someuser'}
]),
},
SetOptions(merge: true),
);
// I used set method along with setOptions in order not to
// override other fields (e.g modifiedBy field)
Keep in mind that if your array is going to be very large, it is better to store the notes as a subcollection.
##EDIT##
If you want to update a nested array, you can use the dot notation.
class SchedulerEvents {
final List<Map<String, dynamic>>? notes;
SchedulerEvents({required this.notes});
}
// lets assume we have data like this that we want to update
final data = SchedulerEvents(
notes: [
{'date': 'sometime', 'notes': 'somenotes', 'user': 'someuser'},
],
);
FirebaseFirestore.instance.doc('path/to/doc').set(
{
'schedulerEvents.notes': FieldValue.arrayUnion([data.notes![0]]),
// or
'schedulerEvents.notes': FieldValue.arrayUnion(data.notes![0]),
// 'schedulerEvents.lname: 'test', // to update other fields
},
SetOptions(merge: true),
);
// we used dot notation to update fields here.
Related
I'm trying to add a new collection with two values in it's document, one is a string and the other is an array.
it worked when I added the string without the array, so my problem is with the array.
this is the function I wrote to set the new collection
void addCategory({required String addedCategory, required List tags}) {
FirebaseFirestore.instance.collection("Category").doc(addedCategory).set({
'name': addedCategory,
'Tags': [tags]
}).then((value) {
emit(CategoryAdded());
});
}
and this the calling
UserCubit.get(context).addCategory(
addedCategory: 'Resturants', tags: ['Italian', 'Romanian']
);
If anyone can help I'll be thankful.
Your tags is already a list and it should not be sent as list<list>
void addCategory({required String addedCategory, required List tags}) {
FirebaseFirestore.instance.collection("Category").doc(addedCategory).set({
'name': addedCategory,
'Tags': [tags], // remove this line
'Tags': tags, // add this line
}).then((value) {
emit(CategoryAdded());
});
}
I’m working on an app with Flutter.
Each of my users has a map with their to-dos stored in cloud firestore:
I want to create a future that updates every to-do to false, which is true.
(In my example, that would be ’sport' and ’eat healthy')
My Problem:
I don’t really know how to do this. . .
I’d be very grateful if someone could help me.
You want to set every field inside the To-Dos map to false which is true. So I would suggest updating every field to false. You might think of checking if it is true and then updating it. But for your use case it is not a good option as anyway you want to set every field to false. Checking and then updating will only incur extra charges for querying. The following should work for your use case.
CollectionReference users = FirebaseFirestore.instance.collection('users');
Future<void> updateUserToDos() {
return users
.doc('your_document_id')
.update({
'To-Dos.Drink Water': false,
'To-Dos.Sport': false,
'To-Dos.eat healthy': false
})
.then((value) => print("User To-Dos Updated"))
.catchError((error) => print("Failed to update user: $error"));
}
To know more about updating document fields of Firestore in Flutter you can refer to this document.
EDIT
So you don’t know which fields are there inside the To-Dos map and you need to update every field inside the To-Dos map to be false. For that you can iterate over the keys of the map and update a new Map having keys same as that of the existing map and values to be false, then update the To-Dos Map field with the updated new Map. You can use the following piece of code. It will update every document inside the users collection.
FirebaseFirestore.instance
.collection('users')
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
var updateMap = new Map();
var toDos = doc['To-Dos'];
for (var item in toDos.keys) {
updateMap[item] = false;
}
doc.reference.update({"To-Dos":updateMap });
});
});
To update the To-Dos map in any specific document you can do the following -
FirebaseFirestore.instance
.collection('users')
.doc("your_document_id")
.get()
.then((DocumentSnapshot doc) {
var updateMap = new Map();
var toDos = doc['To-Dos'];
for (var item in toDos.keys) {
updateMap[item] = false;
}
doc.reference.update({"To-Dos": updateMap});
});
There is no magic solution here. With your current data model, the only approach is to:
Read all documents
Change any false todo's to `true
Write back the document if you made any changes
The only way to make this cheaper (both in monetary cost as in the time it'll take) is to change #1 to only read documents that have a false value. In your current model that is only possible if To-Dos is an array, and then only for up to 10 values in there with an array-contains-any filter:
collectionRef.where("To-Dos", arrayContainsAny: [{
"Drink Water": false,
"Sport": false,
"eat healthy": false
}]
If the keys in this field are entered by the user, the above may become unfeasible because you can only have up to 10 values in there. The solution in that case would be to change your data model to fit your needs (as is quite common when working with NoSQL databases). For example, you could store a simple top-level hasUncompletedTasks field that you update each time you also write the document, and then use that for the query.
Building a shopping app I added a cart item in my 'users' collection.
cart stored in
and after the user purchase those item I wanted to store those cart items in other collection 'orders'. but when I tried to store those cart items in my order collection firebase throws Invalid data. Nested arrays are not supported.
what is my mistake here?
Here is my Code
onPressed: (){
//orderItem(cartItem);
getData() async {
return await FirebaseFirestore.instance.collection('users').where('id', isEqualTo: FirebaseAuth.instance.currentUser.uid).get();
}
getData().then((val){
if(val.docs.length>0){
//print(val.docs[0].data()['cart']);
// Map map = val.docs[0].data()['cart'];
var list = [val.docs[0].data()['cart']];
// map.entries.forEach((e) {
// list.add();
// });
// List items = [val.docs[0].data()['cart'].toList()];
FirebaseFirestore.instance.collection('orders').doc().set({
"userId": FirebaseAuth.instance.currentUser.uid,
"userName": FirebaseAuth.instance.currentUser.displayName,
"orders": FieldValue.arrayUnion([[val.docs[0].data()['cart']]]),
"price": cartController.totalCartPrice.value
});
}
else{
print("Not Found");
}
});
},
I think the problem is trivial. You accidentally put list inside list.
"orders": FieldValue.arrayUnion([[val.docs[0].data()['cart']]]),
I think you can skip one pair of [ so you have this:
"orders": FieldValue.arrayUnion([val.docs[0].data()['cart']]),
Also... if val.docs[0].data()['cart'] is a list you'll probably need to skip another pair to this:
"orders": FieldValue.arrayUnion(val.docs[0].data()['cart']),
Arrays in a Firestore documents cannot contain arrays so you cannot have have something like:
[
[orderId: 1, orders: [...orders]],
[orderId: 2, orders: [...orders]],
//This is possible
{orderId: 3, orders: [...orders]}
]
If you try adding a document from the console itself, you'll notice the same.
The best you can do is store the orders as a string, [JSON.stringify(ordersObject)]. Then when you fetch the data back you can parse it into as JSON again..
Second option is restructuring your data, it would have ideal to have a separate collection for orders. Then you can use .where() queries to fetch orders of a specific user. However this'll be more expensive (but ideal) because you will be fetching 12 documents (12 reads) if user has 12 orders.
I am getting data from firestore like this
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Users');
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
final allData =
querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
its printing data in list format like this
I/flutter (13500): [{number: +123131, shopname: ishaq shop, id: here i need doc id , lastupdate: , sendQty: }, {number: +1231231, shopname: ishaq shop, lastupdate: , id: gXVR4Sp3Cm3pii97Gh00, sendQty: }, {number: +123131, shopname: ishaq shop, lastupdate: , id: AM8KL6SxvFkCRQqpTjre, sendQty: }]
I just want to get a number from a list. I try something like
print(allData['number']);
print(allData.number);
but not working for me
Try this. If it works you can iterate it through your list.
You convert your data to list here then try to call like a map. The reason why it is wrong. and replace final with list. It works fine
List allData =
querySnapshot.docs.map((doc) => doc.data()).toList()
Try
print(allData[0]['number']);
allData is a List<Map<String, dynamic>>, it means a list of maps (map: also called dictionary in java/swift and other language).
To Access a single variable you need the index of the dictionary first then use the key to access it.
var number = allData.first['number'];
print(number);
Right here I am printing the first item of the list, but you can access any item you want as long as you have its index.
I am having some difficulty figuring out how to make it all work together. Here is what I would like to do:
The model is fetched using $resource from the rest API:
var itemResource = $resource('http://blabla.com/items/:id');
$scope.item = itemResource.get({id: '12345'});
The returned item has some fields among which is one array field that lists the ids of the categories:
{
"item_name: "some value",
"categories": ["cat_id1", "cat_id7", "cat_id8"]
}
In the UI I want these categories to be shown as editable multi select. The user should not operate using ids, but rather he should see and be able to chose string representations which come from the mapping within the application. So in html:
<input type"text" ui-select2="categoryOptions" ng-model="item.categories" />
and also in controller:
var categoryMapping = [
{id: "cat_id1", text: "CategoryAlpha"},
...
{id: "cat_id8", text: "CategoryOmega"},
...
];
$scope.categoryOptions = {
'multiple': true,
'placeholder': 'Chose categories',
'width': 'element',
'data': categoryMapping,
};
Obviously the pieces of code above are not working and I don't know how to make them work to do what I want. ui-select2 wants the model (item.categories) to be an array of objects {id, text} and I want it to store only the ids in the items in the database and have the mapping separate. I can't be the first one to do it, there must be a solution, please help.
Thanks