firebase-database について
firebase-database は、Flutter アプリで Firebase Realtime Database を実装するためのスキルです。データ設計、リアルタイム同期、クエリ、オフライン動作、プレゼンス、安全ルールを扱います。
主な機能
- 01フラットな JSON データの設計
- 02データパスのクエリとインデックス設定
- 03データの読み書きとトランザクション
- 04オフライン永続化とプレゼンスの設定
- 05セキュリティルールの作成とテスト
活用シーン
- 01低遅延の状態同期の構築
- 02プレゼンスと接続状態の追跡
- 03一般的なクエリに沿ったデータパス設計
- 04データベースのアクセス制御と検証ルールのテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ evanca/ flutter-ai-rules --skill firebase-databaseインストール後、Claude Code で/firebase-databaseを入力して読み込まれたか確認します。
詳細
Firebase Realtime Database スキル
このスキルでは、データモデリング、クエリ、リアルタイム同期、オフライン対応、セキュリティルールを含め、Flutter アプリケーションで Firebase Realtime Database を正しく実装する方法を説明します。
使用する場面
シンプルなデータモデル、低遅延同期、またはプレゼンス機能に Firebase Realtime Database を使用する場合は、このスキルを使ってください。複雑なクエリと高いスケーラビリティが求められるリッチなデータモデルには、Cloud Firestore を使用してください。
1. データベースの選択
次のような要件がある場合は、Realtime Database を選択してください。
- シンプルなデータモデルと簡単な検索。
- 極めて低遅延の同期(通常の応答時間は 10ms 未満)。
- デフォルトでサブツリー全体を返す階層クエリ。
- 個々のリーフノードの値に至るまで、あらゆる粒度でデータにアクセスできること。
- 組み込みのプレゼンス機能を利用した頻繁な状態同期。
クエリの柔軟性、スケーラビリティ、高可用性が求められるリッチなデータモデルには、代わりに Cloud Firestore を選択してください。
2. セットアップと構成
flutter pub add firebase_database
import 'package:firebase_database/firebase_database.dart';
// After Firebase.initializeApp():
final DatabaseReference ref = FirebaseDatabase.instance.ref();
- ユーザーに最も近いデータベースのロケーションを選択します。
- オフライン機能を利用できるよう、永続化を有効にします。
FirebaseDatabase.instance.setPersistenceEnabled(true);
FirebaseDatabase.instance.setPersistenceCacheSizeBytes(10000000); // 10MB
セットアップのチェックリスト
FirebaseDatabase.instanceにアクセスする前に、Firebase.initializeApp()が完了することを確認します。- 読み取り/書き込み操作を行う前に、永続化を設定します。
- テスト値を書き込み、読み戻して接続を確認します。
3. データ構造
- データをフラット化された JSON ツリーとして構成し、深いネストは避けます。
- ネストの最大深度は32 レベルです。
- 最も一般的なクエリをサポートするようにデータ構造を設計します。
- リスト形式のデータには、一意の識別子としてプッシュ IDを使用します。
final newPostKey = FirebaseDatabase.instance.ref().child('posts').push().key;
- 必要に応じてデータを非正規化します。Realtime Database は結合をサポートしていません。
- カスタムキーは UTF-8 でエンコードし、最大 768 バイトとします。また、
.$#[]/、ASCII 制御文字 0~31、または 127 を含めることはできません。
フラット化された構造の例
// Instead of nesting chat messages inside rooms:
// rooms/roomId/messages/messageId/...
// Flatten into separate top-level paths:
// rooms/roomId: { name: "General", createdBy: "uid1" }
// room-members/roomId: { uid1: true, uid2: true }
// room-messages/roomId/messageId: { text: "Hello", sender: "uid1", timestamp: ... }
このパターンを使うと、すべてのメッセージをダウンロードせずにルームのメタデータを読み取れます。
4. インデックス作成とクエリ
- クエリでは、プロパティの並べ替えまたはフィルタリングのどちらかを行えますが、同じクエリで両方を行うことはできません。
- 頻繁にクエリするフィールドには、セキュリティルールで
.indexOnを使用してインデックスを作成します。
{
"rules": {
"dinosaurs": {
".indexOn": ["height", "length"]
}
}
}
- クエリはデフォルトで階層的で、サブツリー全体を返します。
orderByChild()、orderByKey()、またはorderByValue()で並べ替えます。
final query = FirebaseDatabase.instance.ref("dinosaurs").orderByChild("height");
limitToFirst()またはlimitToLast()で結果を制限します。
final query = ref.orderByChild("height").limitToFirst(10);
startAt()、endAt()、equalTo()を使って範囲クエリを実行します。
// Find users whose name starts with "A"
final query = ref.child("users")
.orderByChild("name")
.startAt("A")
.endAt("A\uf8ff");
5. 読み取りと書き込み操作
一度だけ読み取る:
final snapshot = await FirebaseDatabase.instance.ref('users/123').get();
if (snapshot.exists) {
print(snapshot.value);
}
リアルタイムリスナー:
final subscription = FirebaseDatabase.instance
.ref('users/123')
.onValue
.listen((event) {
final data = event.snapshot.value;
print(data);
});
// Cancel when no longer needed:
subscription.cancel();
DatabaseEvent は、子要素の変更を含め、参照先のデータが変更されるたびに発生します。
書き込み(置き換え):
await ref.set({
"name": "John",
"age": 18,
"created_at": ServerValue.timestamp,
});
更新(一部):
await ref.update({"age": 19});
アトミックトランザクション:
final result = await FirebaseDatabase.instance
.ref('posts/123/likes')
.runTransaction((currentValue) {
return Transaction.success((currentValue as int? ?? 0) + 1);
});
print('Likes: ${result.snapshot.value}');
複数パスのアトミック更新:
final updates = <String, dynamic>{
'posts/$postId': postData,
'user-posts/$uid/$postId': postData,
};
await FirebaseDatabase.instance.ref().update(updates);
- 個々の書き込み操作は 256KB 未満に抑えます。
- ポーリングではなく、リアルタイム更新にはリスナーを使用します。
6. スケーラビリティを考慮した設計
- Realtime Database は、データベースあたり約 200,000 件の同時接続と毎秒 1,000 回の書き込みに対応します。さらに高いスケーラビリティが必要な場合は、複数のデータベースインスタンスにデータをシャーディングします。
- 大きなバイナリデータの保存は避け、ファイルには Firebase Storage を使用します。
- 一貫した時刻管理にはサーバータイムスタンプを使用します。
await FirebaseDatabase.instance.ref('posts/123/timestamp').set(ServerValue.timestamp);
- 複数のパスからアクセスするデータには、ファンアウトパターンを実装します。
- 深いネストはデータ取得時のパフォーマンス問題につながるため、避けます。
7. オフライン機能とプレゼンス
FirebaseDatabase.instance.setPersistenceEnabled(true);
// Keep critical paths synced when offline
FirebaseDatabase.instance.ref('important-data').keepSynced(true);
接続状態とプレゼンス
// Detect connection state
FirebaseDatabase.instance.ref('.info/connected').onValue.listen((event) {
final connected = event.snapshot.value as bool? ?? false;
if (connected) {
// Set online status and configure onDisconnect cleanup
final presenceRef = FirebaseDatabase.instance.ref('status/${uid}');
presenceRef.set({'online': true, 'last_seen': ServerValue.timestamp});
presenceRef.onDisconnect().set({
'online': false,
'last_seen': ServerValue.timestamp,
});
}
});
- 値イベント(
onValue)を使ってデータを読み取り、更新の通知を受け取ります。これはオンライン/オフラインの切り替わりに合わせて最適化されています。 - データが一度だけ必要な場合は
get()のみを使用します。サーバーに接続できない場合は、ローカルキャッシュを確認します。 onDisconnect()の操作はサーバー側で実行されるため、アプリがクラッシュしてもクリーンアップが確実に行われます。
8. セキュリティ
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
.read、.write、.validate、.indexOnを使用して、アクセスを制御し、データを検証します。- Realtime Database では、読み取りルールと書き込みルールはカスケードします。親ルールでアクセスが許可されている場合、子ルールでその許可を取り消すことはできません。
- セキュリティルール内で
auth変数を使用して、ユーザーを認証します。
データ検証の例
{
"rules": {
"messages": {
"$messageId": {
".validate": "newData.hasChildren(['text', 'sender', 'timestamp'])",
"text": {
".validate": "newData.isString() && newData.val().length <= 500"
}
}
}
}
}
- Firebase コンソールのルールシミュレーターを使用して、ルールを十分にテストします。
- ローカルテストには Firebase Emulator Suite を使用します。
参考資料
コメント
Markdown 対応