6

Using flutter, when updating an array with 2 or more consecutive identical values, firestore only adds one.

Example :

void updateValue() {
var now = [new DateTime.now()];
int pain =5;
 Firestore.instance.collection('Patient').document('bC2ML7LaQ1fWGOl37ZUq').collection('Symptom').document('2H6e99Bvrz6h0HBzgnyg')
            .updateData({ 'nauseaLevel' : FieldValue.arrayUnion([pain]), 'nauseaTime':FieldValue.arrayUnion(now)});
          }

When executing this function 2 times, the array "nauseaLevel" in firestore will only add one "pain" value and ignore the second.

the array nauseaTime works as expected as the values are different.

Youssef El Behi
  • 3,042
  • 2
  • 13
  • 22

6 Answers6

15

According to the official documentation regarding updating elements in an array:

arrayUnion() adds elements to an array but only elements not already present.

So there is no way you can add duplicate elements within an array in Cloud Firestore using arrayUnion() function.

To avoid a misunderstanding, however, you can add duplicate elements within an array but only if you read the entire array client side, do the additions (with all duplicates) and then write it back to the database.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • 6
    This is not entirely true. An array can store duplicate items. It just can't be updated with duplicates using arrayUnion. – Doug Stevenson May 13 '19 at 16:09
  • 3
    That's so stupid from their side given the fact that you can add a duplicate using the add button on their console. If you want to call it an array, then let it be an array and open a regular adding API! Otherwise just call it a Set! – Alaa M. Jul 10 '20 at 12:05
  • 1
    Just wasted hour debugging this before realizing this, this should be noted in the array-union docs. – August Kimo Feb 27 '21 at 19:07
2

You can't use arrayUnion to manage an array that must contain duplicates, as you can tell from the definition of arrayUnion.

What you can do instead is read the document, modify the array in the client to be what you want (including duplicates), and write the array field back to Firestore. You can do this atomically with a transaction.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Can you please send a set of code for doing this? Or just guide me for it, I'm using flutter but I want to do the same thing – Hassan Ansari Dec 30 '19 at 09:29
  • This is incredibly inefficient and expensive for large arrays. Is there no other way of doing this?! – Dennis Ashford Apr 26 '23 at 16:46
  • @DennisAshford Large arrays should probably instead be documents in a subcollection anyway. Field arrays are not meant for storing large amounts of data. – Doug Stevenson Apr 26 '23 at 18:02
0

I wrote an article on handling nested objects with Flutter and Firestore and it has some details on how to do this.

Here's my example of retreiving an array, updating it, and saving it back to Firestore:

void AddObjectToArray() {
Exercise exercise;
Set newSet;
Firestore.instance
    .collection("exercises")
    .document("docID")
    .get()
    .then((docSnapshot) => {
  newSet = Set(10, 30), 
  exercise = Exercise.fromMap(docSnapshot.data), 
  exercise.sets.add(newSet),
  Firestore.instance.collection("exercises").document("docID").setData(exercise.toMap())
  });
}

And my Exercise and Set Classes:

Exercise

class Exercise {
final String name;
final String muscle;


List<dynamic> sets = [];

  Exercise(this.name, this.muscle);

  Map<String, dynamic> toMap() => {"name": this.name, "muscle": this.muscle, "sets": firestoreSets()};

  List<Map<String,dynamic>> firestoreSets() {
    List<Map<String,dynamic>> convertedSets = [];
    this.sets.forEach((set) {
      Set thisSet = set as Set;
      convertedSets.add(thisSet.toMap());
    });
    return convertedSets;
  }

  Exercise.fromMap(Map<dynamic, dynamic> map)
      : name = map['name'],
        muscle = map['muscle'],
        sets = map['sets'].map((set) {
          return Set.fromMap(set);
        }).toList();
}

Set

class Set {


final int reps;
  final int weight;

  Set(this.reps, this.weight);

  Map<String, dynamic> toMap() => {"reps": this.reps, "weight": this.weight};

  Set.fromMap(Map<dynamic, dynamic> map)
      : reps = map["reps"].toInt(),
        weight = map["weight"].toInt();
}
Code on the Rocks
  • 11,488
  • 3
  • 53
  • 61
0

i had the same issue with my angular project, however i created a temporary array in my application where i push my data in that array first and then add the temporary array to the firestore and it worked

tempOrder: order = [];


chosenMeal(meal: string, price: number){
   this.tempOrder.push( {meal, price} );
   this.db.collection('tables').doc(this.table.propertyId).update({order:(this.tempOrder)})
}

as you can see from the example, first i add the chosen meal along with the price as an object to my temporary array tempOrder and then on the next line i update my order array in firestore with the data from the tempOrder array

screenshot

as you can see the wanted result in firestore

besim
  • 147
  • 1
  • 11
0
const [myA, setmyA] = useState([])
    const subAnswer = async (ant) => {
    
        if (currentQuestion < 25) {
          myA.push({
          submittedanswer: ant.ant,
          isCorrect: ant.isCorrect,
        },)
        const docRef = doc(db, 'quiz1', `${submittedansid}`)
        await updateDoc(docRef, {
          gevaar: myA })
         }
        }

this worked for my react app

0

according to documentation "arrayUnion() adds elements to an array but only elements not already present.". There are two ways you can circumvent it, One way is that you can add duplicate elements within an array by reading the entire array client side, do the additions (with duplicates) and then write it back to the database but that definitely isn't optimal. Another "Trick" you can do is by using some client side logic you can check if the same object already exists and in that case slightly modify the object and then do arrayUnion()

ahmed
  • 421
  • 6
  • 13