Search code examples
arraysflutterfirebasegoogle-cloud-firestoretransactions

Firebase ArrayUnion crashing app on update


We are trying to update an array field within firebase via a transaction though every time we execute it states the document has been updated but the iOS app crashes unexpectedly and the document when checking the Firebase console hasn't been modified at all

static void createQuestionThread({
    required BuildContext context,
    required String questionId,
    required String ownerId,
    required Task task,
    required String body,
  }) async {
    if (body.isNotEmpty) {
      var questionRef =
          tasksRef.doc(task.id).collection('questions').doc(questionId);

      try {
        await FirebaseFirestore.instance.runTransaction((transaction) async {
          DocumentSnapshot questionSnapshot =
              await transaction.get(questionRef);
          if (questionSnapshot.exists) {
            List<dynamic> threads =
                List.from(questionSnapshot.get('threads') ?? []);
            threads.add({
              'body': body,
              'createdAt': FieldValue.serverTimestamp(),
              'ownerId': ownerId,
            });
            transaction.update(questionRef, {'threads': threads});
            print('Document updated successfully');

            // Show success SnackBar
            ScaffoldMessenger.of(context)
              ..removeCurrentSnackBar()
              ..showSnackBar(
                const SnackBar(
                  content: Text('Your question has been posted'),
                ),
              );
          } else {
            print('Question document does not exist');

            // Show error SnackBar
            ScaffoldMessenger.of(context)
              ..removeCurrentSnackBar()
              ..showSnackBar(
                const SnackBar(
                  content: Text('Failed to post question. Please try again.'),
                ),
              );
          }
        });
      } on FirebaseException catch (e) {
        print('Firestore error: ${e.message}');
        print('Error code: ${e.code}');
        print('Error details: ${e.stackTrace}');

        // Show error SnackBar
        ScaffoldMessenger.of(context)
          ..removeCurrentSnackBar()
          ..showSnackBar(
            const SnackBar(
              content: Text('An error occurred while posting the question.'),
            ),
          );
      } catch (e) {
        print('Error updating document: $e');

        // Show error SnackBar
        ScaffoldMessenger.of(context)
          ..removeCurrentSnackBar()
          ..showSnackBar(
            const SnackBar(
              content: Text('An error occurred while posting the question.'),
            ),
          );
      }
    } else {
      // Show error SnackBar for empty body
      ScaffoldMessenger.of(context)
        ..removeCurrentSnackBar()
        ..showSnackBar(
          const SnackBar(
            content: Text('Please enter a question before posting.'),
          ),
        );
    }
  }

Firebase rules

// Questions
      match /questions/{questionId} {
        allow read: if isLoggedIn();
        allow create: if request.resource.data.body is string &&
            request.resource.data.body != null &&
                request.resource.data.body.trim() != "" &&
          request.resource.data.ownerId is string &&
          request.resource.data.createdAt is timestamp;
        allow update: if isLoggedIn() &&
          // Check if only the threads field is being updated
          request.resource.data.diff(resource.data).affectedKeys().hasOnly(['threads']) &&
          // Check if the threads field is an array
          request.resource.data.threads is list &&
          // Check if each thread in the array is a map with the required fields
          request.resource.data.threads.hasAll(['body', 'createdAt', 'ownerId'])
      }

Document to modified with the field threads adding a new set of maps to the existing array


Solution

  • Your code seems to add an item to an (presumably) array field named threads, but your security rules seem to assume that all subfields of those items exist on the document itself.

    I don't think there's a way to enforce the types of the items in an array field this way, so consider making threads a subcollection of the question document so that you can enforce these rules there.