How to use arrayUnion in set operation

Viewed 870

I am trying to perform FieldValue.arrayUnion() on set(), but it is throwing an Exception. I'm using set instead of update, because I want it to create the document if not exists.

Code (in my case its Batch Write, but I think the principle is same)

WriteBatch batch = db.batch();
MyModel myModel = new MyModel("rwTEPzn9vjyhZCZxFeq8", "Mangesh"); // sample data
batch.set(docRef, FieldValue.arrayUnion(myModel), SetOptions.mergeFields("items"));  // throws exception

Stack trace:

W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.firestore.v1.Value$ValueTypeCase com.google.firestore.v1.Value.getValueTypeCase()' on a null object reference
W/System.err:     at com.google.firebase.firestore.UserDataReader.convertAndParseDocumentData(com.google.firebase:firebase-firestore@@21.4.2:233)
W/System.err:     at com.google.firebase.firestore.UserDataReader.parseMergeData(com.google.firebase:firebase-firestore@@21.4.2:87)
W/System.err:     at com.google.firebase.firestore.WriteBatch.set(com.google.firebase:firebase-firestore@@21.4.2:89)
W/System.err:     at com.abc.AbcActivity.addTrans(AbcActivity.java:604)
W/System.err:     at com.abc.AbcActivity.validateInputs(AbcActivity.java:571)
W/System.err:     at com.abc.AbcActivity.onAddRecord(AbcActivity.java:260)
W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
W/System.err:     at android.view.View$DeclaredOnClickListener.onClick(View.java:5989)
W/System.err:     at android.view.View.performClick(View.java:7140)
W/System.err:     at android.view.View.performClickInternal(View.java:7117)
W/System.err:     at android.view.View.access$3500(View.java:801)
W/System.err:     at android.view.View$PerformClick.run(View.java:27351)
W/System.err:     at android.os.Handler.handleCallback(Handler.java:883)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:100)
W/System.err:     at android.os.Looper.loop(Looper.java:214)
W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:7356)
W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

UPDATE 1: MyModel is a very simple POJO class with couple of strings.

public class MyModel {
    private String id, name;

    public MyModel (String id, String name) {
        this.id = id;
        this.name = name;
    }

    @PropertyName("id")
    public String getId() {
        return id;
    }

    @PropertyName("id")
    public void setId(String id) {
        this.id = id;
    }

    @PropertyName("name")
    public String getName() {
        return name;
    }

    @PropertyName("name")
    public void setName(String name) {
        this.name= name;
    }
}

UPDATE 2: The exception is thrown for String and int values as well. That is, neither FieldValue.arrayUnion("abc") nor FieldValue.arrayUnion(1) works. I have also observed the same behaviour in case of arrayRemove().

UPDATE 3: Here is a screenshot of my db.

DB

UPDATE 4: I tested set outside of batch, directly on the document. It fails. Note that items was emptied before doing this to avoid any type-related conflicts.

docRef.set(FieldValue.arrayUnion("abc"), SetOptions.mergeFields("items"));

Looks like it is impossible to use arrayUnion and arrayRemove with set, despite of the documentation that says: "returns a special value that can be used with set() or update()"

2 Answers

Finally, I found a solution. Here is the code:

Map<String, Object> map = new HashMap<>();
map.put("items", FieldValue.arrayUnion(myModel));
batch.set(docRef, map, SetOptions.merge());

This creates the document if not exists and updates items array. Note that, SetOptions.merge() is used instead of SetOptions.mergeFields().

None of those method calls will work because your items array contains MyModel objects and not strings or numbers. The simplest method I can think of, it would be to get that array as a list of MyModel objects (List<MyModel>) and remove/add elements according to your needs.

Please also note, that in order to remove an entire object from the array, you should pass to that method, the entire object, with all fields not only partial data.

Edit:

How arrayUnion will get to know which array to update?

It will know because we need to pass as the first argument the name of your array property. So please use the following lines of code:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
WriteBatch batch = rootRef.batch();
DocumentReference docRef = rootRef.collection("yourColl").document("docId");
batch.update(docRef, "items", FieldValue.arrayUnion("abc"));
batch.commit();

The result will be the updated of your items array with a new element which is abc.

Related