how to write nested array in Firestore using flutter - flutter

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.

Related

Flutter FireStore Create, Read and Update Array with a Map Object

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.

how to query for key exists in object field in firestore document (firebase web sdk)

I have subjects collection. In this collection every document has tutors field that is object where key is id of tutors( from tutors collection)
tutors: {
"tutor_id_1": {
"name": "jonas",
"email": "jonas#gmail.com"
},
"tutor_id_2":{
"name": "stephen",
"email": "stephen#gmail.com"
},
"tutor_id_3":{
"name": "maria",
"email":"maria#gmail.com"
}
}
So how to query subjects where tutors field contain tutor id equal to "tutor_id_1" ?
I found one way
if I have two variables in client side
const tutorToFindId = "xxx"
const tutorToFindEmail = "YYY"
query(
collection(db, 'subjects'),
where(`tutors.${tutorToFindId}.email`, '==', `${tutorToFindEmail}`)
),
Is there any other way ??
As I understand, "tutor_id_1" is being used as a unique id. Considering that, you may structure your data model with "tutors" as a subcollection instead of a field and you will be able to get the content of that specific document as follows:
const docRef = db.collection('subjects').doc('subjectX').collection('tutors').doc(tutorToFindId);
const tutor = await docRef.get();
if (!tutor.exists) {
console.log('No such document!');
} else {
console.log('Document data:', tutor.data());
}
db.getBy({ where: { [`tutors.${tutorToFindId}.email`]: tutorToFindEmail } });
Take a look at the getBy function in https://www.npmjs.com/package/firebase-firestore-helper
Disclaimer: I am the creator of this library. It helps to manipulate objects in Firebase Firestore (and adds Cache)
Enjoy!

Why can't I refer to a nested object in React Native in my mongoDB?

I got a simple question about object reference which seems pretty strait forward to me (I'm new to programming). When I get an item from my mongDB through this function:
const workAPI = async () => {
const response = await trackerAPI.get("/workouts");
setResults(response);
console.log(results);
};
I get my list of workouts, which are objects. One workout object looks like this:
Object {
"__v": 0,
"_id": "6183a09df3ba372d5d182ee3",
"exercises": Array [
Object {
"_id": "6183a09df3ba372d5d182ee4",
"exerciseProps": Object {
"reps": 4,
"sets": 3,
"time": 20,
},
"exerciseTitle": "running",
},
],
"title": "Jdbsv",
"userId": "615e06f36ce5e5f1a69c675e",
So everything is fine, I have my whole list of workouts logged, but why can't I log a property from a workout through the following function:
const workAPI = async () => {
const response = await trackerAPI.get("/workouts");
setResults(response);
console.log(results._id);
};
The _id is a property every workout has, I also tried to fill in an actual _id through a string (that's what I want to use it for), but neither did that work. Is it only possible to refer to the _id through mapping?
Thanks in advance!
It seems your api returns an array of workout. At first you should select an element from the array, say first element, and then log the _id property of that element.
console.log(results[0]._id)
Not to mention that the zero here is called the index of that element in the array and the element indexes start from 0.

How to delete specific map field in an array?

I created an firestore collection with users, and in this one I save favourites for an favourite list by an array, and the favourites get created as map fields of this array, and I want to know how I can remove an specific favourite (map field) of my array?
That is my function to add favourites to my array.
addFav(name, performer, category, room, abstract, begin, dis, color, btn, id`){
try{
this.afs.doc(`users/${this.user.getUID()}`).update({
favs: firestore.FieldValue.arrayUnion({
name,
performer,
category,
room,
abstract,
begin,
dis,
color,
btn
})
})
}
I need help to delete an specific favourite / map field of my array
deleteFav()
{
try{
this.afs.doc(`users/${this.user.getUID()}`).update({
favs: firestore.FieldValue.delete()
})
}
}
following pictures shows my firestore collections with the array and the map fields.
https://i.stack.imgur.com/1Y1Xj.png
You should use arrayRemove method like:
favs: firestore.FieldValue.arrayRemove("performer")
Check this one:
https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

Dataloader did not return an array of the same length?

I Am building an express JS application with graphql, and mongodb (mongoose). I am using facebooks Dataloader to batch and cache requests.
Its working perfectly fine except for this use case.
I have a database filled with users posts. Each post contains the users ID for reference. When i make a call to return all the posts in the database. The posts are returned fine but if i try to get the user in each post. Users with multiple posts will only return a single user because the key for the second user is cached. So 2 posts(keys) from user "x" will only return 1 user object "x".
However Dataloader has to return the same amount of promises as keys that it recieves.
It has a option to specify cache as false so each key will make a request. But this doesnt seem to work for my use case.
Sorry if i havn't explained this very well.
this is my graphql request
query {
getAllPosts {
_id // This is returned fine
user {
_id
}
}
}
Returned error:
DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise of an Array of the same length as the Array of keys.
are you trying to batch post keys [1, 2, 3] and expecting to get user results [{ user1 }, {user2}, { user1 }]?
or are you trying to batch user keys [1, 2] and expecting to get post results [{ post1}, {post3}] and [{ post2 }]?
seems like only in the second case will you run into a situation where you have length of keys differing from length of results array.
to solve the second, you could do something like this in sql:
const loader = new Dataloader(userIDs => {
const promises = userIDs.map(id => {
return db('user_posts')
.where('user_id', id);
});
return Promise.all(promises);
})
loader.load(1)
loader.load(2)
so you return [[{ post1}, {post3}], [{ post2 }]] which dataloader can unwrap.
if you had done this instead:
const loader = new Dataloader(userIDs => {
return db('user_posts')
.where('user_id', [userIDs]);
})
loader.load(1)
loader.load(2)
you will instead get [{ post1}, {post3}, { post2 }] and hence the error: the function did not return a Promise of an Array of the same length as the Array of keys
not sure if the above is relevant / helpful. i can revise if you can provide a snippet of your batch load function
You need to map the data returned from the database to the Array of keys.
Dataloader: The Array of values must be the same length as the Array of keys
This issue is well explained in this YouTube Video - Dataloader - Highly recommended