FlutterFlow × Firestore で「動いてるように見えるけど、実は動いていない」現象に遭遇したので、原因と対策を共有します。
TL;DR
where2つ + 別フィールドのorderByは複合インデックス必須- 未作成だと Firestore は
FAILED_PRECONDITIONを返すが、FlutterFlow 標準の Backend Query はこれを UI に伝えない(Empty State と同じ描画になる) - 結果、画面は出てるのにデータが 0 件・想定外という「動いて見える」状態に
- 対策は「DevTools/ログで検知」+「Custom Action でラップ」+「
firestore.indexes.jsonを Git 管理」の 3 点セット

症状
FlutterFlow の Backend Query で、こんなクエリを ListView に紐付けていました。
FirebaseFirestore.instance
.collection('articles')
.where('category', isEqualTo: keyword)
.where('isPublished', isEqualTo: true)
.orderBy('priority')
.get();
プレビューでは画面が普通に描画される。エラーダイアログも出ない。でも ListView の中身が 0 件 or 想定と違うデータ表示。Test Mode では動くのに Run Mode で症状が出ることもあり、再現性が掴みにくい状態でした。
原因と FlutterFlow の盲点
ご存知の通り、where 2つ + 異なるフィールドの orderBy は複合インデックス必須で、未作成なら Firestore は FAILED_PRECONDITION を返します。問題はそこではなく、そのエラーが FlutterFlow の UI まで届いていなかったことです。
FlutterFlow 標準の Backend Query は、明示的に Error State を設定しないと、エラー時のレンダリングが Empty State と区別がつきません。生成された Flutter コードを覗くと、StreamBuilder / FutureBuilder の snapshot.hasError 分岐が単に空のウィジェットを返す形になっており、例外オブジェクト自体は snapshot.error に存在するのに UI 層で握りつぶされています。
この挙動は FlutterFlow 公式の GitHub でもバグとして受理されています。
Issue #1169 – Firestore Query, in Action. UI does not detect missing indexes
type: bug/status: confirmedで受理 → 修正済み
ただし修正されたのは エディタ側でのビルド時検知の話で、実行時に UI へエラーが伝わらない挙動は別問題として依然残っています。コミュニティでも “Catching Errors in Backend Queries” 等で頻繁に話題になっており、Custom Action による上書きが現実解とされています。
豆知識: Issue #1169 で言及されている Workaround
Action 内で Firestore Query を呼ぶ場合、エディタが index 不足を検知しないバグがありました。同じクエリを任意の Widget の Backend Query にも仕込んでおくと、エディタが「Create Index」プロンプトを出してくれる、というのが Issue 内で共有された Workaround です。今でも保険として有効です。
対策1(最優先): ログで検知する
Firestore は FAILED_PRECONDITION を必ずログに出力します。エラーメッセージ内に Firebase Console への 「Create Index」リンクが含まれているので、そこから直接 index を作成するのが最速ルートです。
- Web ビルド (Run / Preview): ブラウザの DevTools Console を常時開く
- iOS ビルド: Xcode のコンソール、または
flutter logs - Android ビルド: Android Studio Logcat、または
flutter logs
「画面が動いてる風」でも、ログに FAILED_PRECONDITION: The query requires an index が出ていれば確定です。
対策2: Custom Action でラップしてエラーを 3 層で出し分ける
重要画面は Backend Query をそのまま使わず、Custom Action でクエリを実装してエラーハンドリングを自前で握ります。FlutterFlow 18 以降なら Backend Query Loading Widget の Error State も使えますが、Crashlytics 連携や環境別出し分けまで含めるなら Custom Action が確実です。

- 開発時 (kDebugMode): 詳細スタックトレースを App State 経由で画面に表示
- 本番ユーザー: 「データ取得に失敗しました」等の汎用日本語
- 運用: Crashlytics に自動送信して開発者が静かに把握
事前準備
- FlutterFlow → Settings → App State に
lastError(String) とuserFacingError(String) を追加 - FlutterFlow → Settings → Project Dependencies に
firebase_crashlyticsを追加 + Crashlytics 有効化 - Custom Action の Return Type は
List<ArticleRecord>(FlutterFlow 生成のレコードクラス)に設定
実装例
Future<List<ArticleRecord>> fetchArticles(String keyword) async {
try {
final snapshot = await FirebaseFirestore.instance
.collection('articles')
.where('category', isEqualTo: keyword)
.where('isPublished', isEqualTo: true)
.orderBy('priority')
.get();
return snapshot.docs
.map((d) => ArticleRecord.fromSnapshot(d))
.toList();
} on FirebaseException catch (e) {
// 運用: Crashlytics に必ず送る(本番でも静かに記録)
FirebaseCrashlytics.instance.recordError(
e, e.stackTrace, fatal: false,
);
// 開発時のみ画面に詳細を出す(本番ビルドでは無視)
if (kDebugMode) {
FFAppState().update(() {
FFAppState().lastError = '${e.code}: ${e.message}';
});
}
// ユーザー向けはエラーコードで分岐
FFAppState().update(() {
FFAppState().userFacingError = _toUserMessage(e);
});
return []; // クラッシュさせず空配列で続行
}
}
String _toUserMessage(FirebaseException e) {
switch (e.code) {
case 'permission-denied':
return 'アクセス権限がありません。ログイン状態をご確認ください。';
case 'unavailable':
return 'ネットワーク接続を確認してください。';
case 'failed-precondition':
case 'unknown':
default:
return 'データの取得に失敗しました。時間をおいて再度お試しください。';
}
}
開発画面の隅に FFAppState().lastError を Conditional Visibility (kDebugMode == true) で常駐させておくと、握りつぶしが発生した瞬間に視認できます。本番ビルドでは kDebugMode が false なので絶対に表示されません。
対策3: firestore.indexes.json を Git 管理する
個別対応だけだと属人化するので、index 定義そのものをリポジトリで管理します。FlutterFlow が自動生成してくれるわけではないので、Firebase CLI で手動セットアップが必要です。
npm install -g firebase-tools
firebase login
firebase init firestore # firestore.indexes.json を作成
# 既存 index を引き出す:
firebase firestore:indexes > firestore.indexes.json
# クエリ追加時はこのファイルを更新して:
firebase deploy --only firestore:indexes
- クエリ追加時に index 定義も更新する運用にする
- PR レビューで「クエリ追加してるのに index 未追加」を弾く
- CI で
firebase deploy --only firestore:indexesを自動実行
Firebase Console での手動作業を排除できるので、本番デプロイ時の index 作成漏れを構造的に防げます。
チェックリスト
- 開発中はブラウザ DevTools Console /
flutter logsを常に確認 - 重要画面は Backend Query を Custom Action でラップ
- App State に
lastError/userFacingErrorを追加し、kDebugMode で出し分け - Crashlytics を有効化して本番エラーを自動収集
firestore.indexes.jsonを Git 管理し CI で deploy- Action 内で使うクエリは保険として Widget Backend Query にも置く(Issue #1169 Workaround)
まとめ
FlutterFlow で「動いて見える」は「動いている」とイコールではありません。Firestore のインデックス不足エラーが UI に届かないまま空配列にすり替わるパターンが構造的に存在します。
ログ検知・Custom Action ラップ・Crashlytics 連携・firestore.indexes.json Git 管理の 4 点を最初に組んでおくと、本番でハマる時間を大幅に減らせます。
同じ症状で困っている開発者の参考になれば幸いです

