👏

【 Flutter限定エラー 】FieldValue.serverTimestamp()をsnapshotsで監視したら2回実行されるバグ

2023/01/18に公開

Firestoreにバグが存在するなんて思いもよらなんだ。
そう言えば、whereNotInとかも謎に取得できなかったっけ。

バグの元凶
FieldValue.serverTimestamp()

なんかバグを言語化するのが凄いめんど...難しいので、
ぜひ、コードで理解してもらえると助かります。

では、行ってみよう

まずは、testsってコレクションを監視する

_db.collection('tests')
   .snapshots()
   .listen((value) {
     for (var element in event.docChanges) {
       print('element: ${element.doc.data()}');
     }
   }

で、そのtestsコレクションに
createdAt と index を突っ込んでみる

テストで送信するコード
_db.collection('tests').doc().set({
  'createdAt': FieldValue.serverTimestamp(),
  'index': index,
});

その結果、得られたログがこちら↓

ログ
flutter: element: {createdAt: null, index: 1290}
flutter: element: {createdAt: Timestamp(seconds=1674034777, nanoseconds=677000000), index: 1290}

これは、2回作成した訳ではなく、1回しか作成していません。
ですが、謎に2回リッスンしてますね。

原因はぶっちゃけ謎。
下記で同じようなエラーがあって、どうするべきかみたいな話が上がっている。

https://stackoverflow.com/questions/62830142/how-to-ensure-the-fieldvalue-servertimestamp-finish-executing-in-cloud-firesto

個人的には、1回のread料金のつもりが謎に2回分になってしまうので
正直イラっとしますが、まぁ、しゃーない。俺にGoogleを動かす力はまだないから。

エラーをどう回避するのか

本当はここのcreatedAtはnullを許容する訳ではなく、
むしろrequiredなのですが、変わらなさそうなら、こっち側が対応するしかない。

本来はrequiredの部分がnullになっているので、
nullの時は処理をしないみたいな感じでコードを組み直せば問題なさそう

下記みたいな感じで

var doc = qs.docs[index].data() as Map<String, dynamic>;
if (doc['createdAt'] == null) return Container();

Functionsでは、どうなっているのか

functionsでは下記のコードのようにテストしてみたが、
どうやらcreatedAtは最初から入力されているようでした。

なので、Flutterでクライアントから監視する場合のみ問題がありそう。

  _db.collection('tests').onSnapshot(snapshot => {
    console.log('test開始');
    if (snapshot.docs.length == 0) {
      console.log('データがないので早期リターン');
      return;
    }
    console.log(snapshot.docs[0].data()['id']);
    console.log(snapshot.docs[0].data()['createdAt']);
  });

これはただ単にリッスンしているメソッドを発火させてるのですが、
onCreate や onUpdate では大丈夫なのか気になったのでテストしてみると
問題なく、onCreateの時だけ発火されていた

テストコード
exports.progressCreated = functions
  .region("asia-northeast1")
  .firestore.document('tests/{testId}')
  .onCreate(async (snap, context) => {
    console.log('作成:データが作成されました');
  })

exports.progressUpdated = functions
  .region("asia-northeast1")
  .firestore.document('tests/{testId}')
  .onUpdate(async (change, context) => {
    console.log('更新:データが更新されました')
  })

設計ミスか知らんけど

まぁ、これらのエラーから分かる通り、
FieldValue.serverTimestamp()は、
一度、ドキュメントが作成されてから再度入力されているんだろうと言うことがわかった。
厳密には、推測ができる。

ただ、何が面倒って、この辺のパッケージに頼り切っている場合のエラー処理って
他は大丈夫なのか心配になるし、それらをテストする必要があるから
何気にしんどい。

公式のパッケージだけはマジでミスしないで欲しいと切実に願ってますmm

申し訳ないけど。。。むり

運営に報告して直してもらいたいけど、
GitHub上でやり取りって英語だし、初心者の自分には到底無理と思ってします・・・

いつか出来る時が来たら良いなぁ〜なんてね。

というか問題報告も2年前という・・・。

追記

FieldValue.serverStamp()でデータを突っ込んだ後に
データを再取得した場合も、どうやらnullになるらしい。

await setData();
await getData();

これくらいのスピード感のノリで行くと、データがnullになってしまっていた。。。辛い。
わざわざ、250ミリ秒くらい待たないとダメっぽい

Discussion