firestore arrayUnionを使ってupdateする

firestoreにarrayの配下にmapがあるようなフィールドをupdateするときに、 プロジェクトのメンバーが複雑な分岐処理を書いていたので、 arrayUnionで簡単になるんじゃない?と思い調べました。

最初メンバーが書いていたコードはこんな感じです。 firestore_refを使ってるので、本当はちょっと違いますが。 あと、テストしてないので、動くかわからないです。

  updateFcmTokens(device, token) async {
    final newFcmToken = {
        "device": device, "token": token
    };
    // まずはusersの特定のドキュメントを取得
    final userDoc = FirebaseFirestore.instance
        .collection('users')
        .doc('uid');
    final snap = await userDoc.get(); // 本当はstreamで取りたい
    final user = snap.data();
    if (user == null) return; // これは例外的なケース
    final fcmTokens = user['fcmTokens'] as List<Map>;
    if (user['fcmTokens'] == null) {
      userDoc.update({
        'fcmTokens': [newFcmToken]
      });
    } else {
      // 同じものがないか調べる
      final isUpdate =
          fcmTokens
              .where((fcmToken) => fcmToken['token'] == token)
              .isEmpty;
      if (isUpdate) {
        // 同じものがない場合は更新

        // ここのaddも冗長
        fcmTokens.add({
            'token': token, 
            'device': device});
        userDoc.update({'fcmTokens': fcmTokens});
      }
    }
  }

こういう時はまず、自分の場合はadminSDKで同じことができるならそっちで検証します。 ちょっと長いので、こちらに置いてあります。

firebase-practice/array_map_update.ts at main · na8esin/firebase-practice · GitHub

ちょっと前に書いたarrayRemoveの検証に似てます。

adminSDKで試してみて、使い方がわかったので、flutterのソースをこんな感じで直しました。

// tokenはログインのタイミングなどでFirebaseMessaging.getToken()した値
updateFcmTokens(device, token) async {
    final fcmToken = {"device": device, "token": token};
    userDoc.update({"fcmTokens": FieldValue.arrayUnion([fcmToken])});
}

単純に書き直すならこれだけでいいと思います。 同じものがある時は、何も追加されずに更新もされないので。 リクエストする回数も最大で1回(get)ありましたが、こちらも1回です。

ただ、ログイン時に必ず、最近保存したfcmTokenをusersなんかと一緒に、firestoreから 取得しているような場合は、FirebaseMessaging.getToken()したときに、 同じものかどうか比較して、余計なupdateを防いでもいいかもしれません。