Flutterアプリケーション開発概論

【インデックスエラー】failed-preconditionとFirestore複合インデックスを理解する

48LINE風チームタスク管理アプリを作りながら、ログイン・データベース・権限管理を学ぶ
FlutteriOSAndroidMacOSWindows基礎から学ぶ開発アプリ開発

このページでやること

このページでは、Firestoreで出ることがある failed-precondition というエラーを理解します。

このエラーは、主にFirestoreで検索や並び替えをしたときに出ます。

failed-precondition

特に、次のようなコードで起こりやすいです。

.where(...)
.orderBy(...)

Firestoreが、

この検索をするには、専用のインデックスが必要です。

と教えてくれている状態です。


今日のゴール

failed-precondition が出ても、慌てずに確認できるようにします。

エラーが出る
↓
エラー文を見る
↓
インデックス作成リンクを開く
↓
Firebase Consoleで作成する
↓
数分待つ
↓
もう一度アプリを動かす

npmや環境変数は必要?

このページでは、npmは使いません。

環境変数も設定しません。

必要なのは、Firebase Consoleの操作だけです。

Flutterコード
↓
Firestoreの検索条件
↓
Firebase Consoleでインデックス作成

まず結論

failed-precondition が出たら、エラー文に表示されるURLを探します。

多くの場合、次のような文章が出ます。

The query requires an index.
You can create it here:
https://console.firebase.google.com/...

このURLを開くと、必要なインデックス作成画面に進めます。

初心者の方は、まずこの方法で進めればOKです。


1. failed-preconditionとは?

Step 1:意味を確認する

failed-precondition は、日本語で言うと、

実行する前に必要な条件が足りません。

という意味です。

Firestoreの場合は、多くの場合こうです。

この検索には、先にインデックスを作ってください。

つまり、コードが全部間違っているわけではありません。

Firestore側の準備が足りない状態です。


Step 2:よく出るエラー文

FlutterのターミナルやDebug Consoleに、次のようなエラーが出ます。

FirebaseException ([cloud_firestore/failed-precondition])
The query requires an index.
You can create it here: https://console.firebase.google.com/...

大事なのはここです。

The query requires an index.

意味は、

この検索にはインデックスが必要です。

です。


2. インデックスとは?

Step 3:インデックスは「目次」

インデックスは、Firestoreがデータを速く探すための「目次」です。

本で考えると分かりやすいです。

目次なし
↓
全部のページを探す
↓
遅い

目次あり
↓
必要な場所をすぐ探せる
↓
速い

Firestoreも同じです。

インデックスなし
↓
探しにくい

インデックスあり
↓
速く検索できる

Step 4:なぜインデックスが必要なのか

たとえば、タスクをこの条件で表示したいとします。

status が todo
さらに
createdAt の新しい順

コードでは、こう書くことがあります。

FirebaseFirestore.instance
    .collection('teams')
    .doc(teamId)
    .collection('tasks')
    .where('status', isEqualTo: 'todo')
    .orderBy('createdAt', descending: true)
    .snapshots();

この検索は、Firestoreから見ると少し複雑です。

statusで絞り込みたい
createdAtで並び替えたい

複数の条件を組み合わせているので、専用のインデックスが必要になることがあります。


3. 単一インデックスと複合インデックス

Step 5:単一インデックス

1つの項目だけを見る検索は、比較的シンプルです。

.where('status', isEqualTo: 'todo')

または、

.orderBy('createdAt')

このような1項目だけの検索は、Firestoreが自動で対応しやすいです。


Step 6:複合インデックス

複数の条件を組み合わせると、複合インデックスが必要になることがあります。

例です。

.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)

または、

.where('status', isEqualTo: 'todo')
.where('priority', isEqualTo: 'high')
.orderBy('createdAt', descending: true)

これは、

status
priority
createdAt

のように、複数の項目を一緒に使っています。

このようなときに、Firestoreが複合インデックスを求めることがあります。


4. このアプリで起こりやすい場所

Step 7:チーム一覧で起こる例

今のチーム一覧では、基本的にこのように取得しています。

FirebaseFirestore.instance
    .collection('teams')
    .where('memberIds', arrayContains: uid)
    .snapshots()

これはシンプルです。

ただし、ここに orderBy を追加すると、インデックスが必要になることがあります。

FirebaseFirestore.instance
    .collection('teams')
    .where('memberIds', arrayContains: uid)
    .orderBy('updatedAt', descending: true)
    .snapshots()

この場合は、

memberIds に自分のuidが入っているチーム
さらに
updatedAt の新しい順

という検索になります。


Step 8:タスク一覧で起こる例

タスク一覧で、ステータス別に表示して、さらに新しい順に並べる場合です。

FirebaseFirestore.instance
    .collection('teams')
    .doc(teamId)
    .collection('tasks')
    .where('status', isEqualTo: 'todo')
    .orderBy('createdAt', descending: true)
    .snapshots()

これは、

statusで絞る
createdAtで並び替える

ので、複合インデックスが必要になることがあります。


Step 9:メンバー一覧で起こる例

メンバー一覧を、権限ごとに絞って、参加日時順に並べる場合です。

FirebaseFirestore.instance
    .collection('teams')
    .doc(teamId)
    .collection('members')
    .where('role', isEqualTo: 'member')
    .orderBy('joinedAt', descending: true)
    .snapshots()

これは、

roleで絞る
joinedAtで並び替える

ので、インデックスが必要になることがあります。


5. エラーが出たときの直し方

Step 10:エラー文のリンクを探す

failed-precondition が出たら、ターミナルやDebug Consoleを見ます。

次のようなURLを探します。

https://console.firebase.google.com/...

Firestoreは、必要なインデックス作成画面へのリンクを出してくれることが多いです。


Step 11:リンクを開く

エラー文のURLをブラウザで開きます。

すると、Firebase Consoleのインデックス作成画面が開きます。

多くの場合、必要な項目が最初から入っています。

Collection ID
Fields
Query scope

初心者の方は、基本的にそのまま作成でOKです。


Step 12:Create indexを押す

画面に表示される作成ボタンを押します。

英語表示の場合です。

Create index

日本語表示の場合です。

インデックスを作成

これで、Firestoreがインデックスを作り始めます。


Step 13:数分待つ

インデックスは、作ってすぐ使えるとは限りません。

作成中
↓
数分待つ
↓
有効

データが少なければ早いです。

データが多いと、少し時間がかかります。


Step 14:アプリをもう一度確認する

インデックスが有効になったら、アプリをもう一度動かします。

flutter run

すでに起動中なら、ホットリスタートします。

R

一覧が表示されれば成功です。


6. リンクが出ない場合

Step 15:Firebase Consoleから手動で開く

エラー文にリンクが出ない場合は、Firebase Consoleから確認します。

Firebase Console
↓
Firestore Database
↓
Indexes

日本語表示の場合です。

Firebase Console
↓
Firestore Database
↓
インデックス

Step 16:Composite indexesを確認する

複合インデックスは、英語では次の名前です。

Composite indexes

日本語表示では、

複合インデックス

のように表示されます。

ここで、必要なインデックスを追加できます。


Step 17:手動作成の例

たとえば、この検索をしたい場合です。

.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)

インデックスは、だいたい次のように作ります。

Collection ID: tasks
Field: status Ascending
Field: createdAt Descending

ただし、手動で作ると間違えやすいです。

基本は、エラー文のリンクから作成するのがおすすめです。


7. 学習中はインデックスを避けてもOK

Step 18:最初はorderByを付けない

初心者のうちは、まずアプリを動かすことを優先します。

インデックスエラーで止まる場合は、いったん orderBy を外してもOKです。

例です。

FirebaseFirestore.instance
    .collection('teams')
    .where('memberIds', arrayContains: uid)
    .snapshots()

このように、まずは絞り込みだけにします。


Step 19:Dart側で並び替える

Firestoreで並び替えずに、取得したあとFlutter側で並び替える方法もあります。

final tasks = snapshot.data?.docs ?? [];

tasks.sort((a, b) {
  final aTime = a.data()['createdAt'] as Timestamp?;
  final bTime = b.data()['createdAt'] as Timestamp?;

  if (aTime == null || bTime == null) {
    return 0;
  }

  return bTime.compareTo(aTime);
});

これは学習中には便利です。

ただし、データが多くなったら、Firestore側で並び替える方がよいです。


Step 20:検索条件を増やしすぎない

最初から検索条件を増やしすぎると、エラーの原因が分かりにくくなります。

最初は、次のようなシンプルな形から始めます。

.collection('tasks').snapshots()

または、

.where('memberIds', arrayContains: uid)

動いたあとで、orderBywhere を追加します。


8. このアプリで作る可能性があるインデックス

チーム一覧用

チーム一覧を新しい順にする場合です。

.where('memberIds', arrayContains: uid)
.orderBy('updatedAt', descending: true)

必要になる可能性があるインデックスです。

Collection ID: teams
memberIds Array contains
updatedAt Descending

タスク一覧用

ステータス別にタスクを表示する場合です。

.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)

必要になる可能性があるインデックスです。

Collection ID: tasks
status Ascending
createdAt Descending

優先度別タスク一覧用

優先度が高いタスクを新しい順に表示する場合です。

.where('priority', isEqualTo: 'high')
.orderBy('createdAt', descending: true)

必要になる可能性があるインデックスです。

Collection ID: tasks
priority Ascending
createdAt Descending

メンバー一覧用

メンバー権限ごとに表示する場合です。

.where('role', isEqualTo: 'member')
.orderBy('joinedAt', descending: true)

必要になる可能性があるインデックスです。

Collection ID: members
role Ascending
joinedAt Descending

9. permission-deniedとの違い

failed-preconditionpermission-denied は、意味が違います。

エラー意味
permission-denied権限がない
failed-precondition必要な準備が足りない
The query requires an indexインデックスが必要

簡単に言うと、こうです。

permission-denied
↓
あなたには許可されていません

failed-precondition
↓
Firestore側の準備が足りません

見る場所も違います。

エラー見る場所
permission-deniedSecurity Rules / role / uid
failed-preconditionIndexes / query / orderBy

10. よくあるエラーと直し方

エラー原因直し方
failed-preconditionインデックス不足エラー文のリンクから作成
The query requires an index複合インデックスが必要Firebase Consoleで作成
作成したのにまだエラーまだ作成中数分待つ
どのインデックスか分からない条件が複雑エラー文のURLを使う
URLが見えないDebug Consoleが短いログ画面を広げる
作っても別のエラーが出る別queryのインデックスが必要新しいエラー文を見る
手動作成で迷うcollectionやfieldが違うURLから作る

初心者向け:まず見る場所3つ

failed-precondition が出たら、まずここだけ確認します。

1. エラー文にURLがあるか
2. where と orderBy を一緒に使っているか
3. Firebase ConsoleのIndexesで作成中になっていないか

最短作業まとめ

読むのが大変な人は、ここだけ見てください。

1. エラー文を見る

failed-precondition
The query requires an index.

2. URLを開く

https://console.firebase.google.com/...

3. インデックスを作成する

Create index / インデックスを作成

4. 数分待つ

作成中 → 有効

5. アプリをもう一度動かす

flutter run

チェックリスト

□ failed-preconditionの意味を理解した
□ インデックスはFirestoreの目次だと理解した
□ where + orderByで起こりやすいと理解した
□ エラー文のURLを探した
□ Firebase ConsoleでIndexesを確認した
□ 必要ならインデックスを作成した
□ 作成中は数分待つと理解した
□ permission-deniedとの違いを理解した
□ 学習中はorderByを外す方法もあると理解した

ミニ確認問題

Q1. failed-precondition はどんなときに出やすいですか?

回答

Firestoreの検索に必要なインデックスがないときに出やすいです。

特に、whereorderBy を組み合わせたときに出ることがあります。


Q2. インデックスとは何ですか?

回答

Firestoreがデータを速く探すための目次です。


Q3. エラー文にFirebase ConsoleのURLがある場合、どうしますか?

回答

URLを開いて、インデックスを作成します。


Q4. インデックスを作ったら、すぐ使えますか?

回答

すぐ使えないことがあります。

作成完了まで、数分待つことがあります。


Q5. permission-deniedfailed-precondition の違いは何ですか?

回答

permission-denied は権限エラーです。

failed-precondition は、インデックスなど必要な準備が足りないエラーです。


このページのまとめ

  • failed-precondition は、Firestoreで必要な準備が足りないときに出る。
  • 多くの場合、複合インデックス不足が原因。
  • whereorderBy を組み合わせると起こりやすい。
  • エラー文にあるFirebase ConsoleのURLから作成するのが簡単。
  • インデックス作成後は、反映まで少し待つ。
  • 学習中は、まず orderBy を外して進めてもOK。
  • permission-denied は権限エラー。
  • failed-precondition はインデックス系のエラー。
  • このページではnpmや環境変数は不要。

次のページでやること

次のページでは、users/{uid} のSecurity Rulesを作ります。

本人だけが自分のプロフィールを読み書きできるようにします。

教材トップへ戻る