Flutter: Firebase統合

FirebaseはFlutterのスーパーアシスタント — Auth、データベース、ストレージ、アナリティクス、プッシュ通知がオールインワンですぐに使えます。

📋 前提条件: 以下に既に慣れている必要があります

1. このレッスンで学ぶこと


2. 自前バックエンド崩壊のリアルなストーリー

(1) 悩み:自前バックエンドの高い運用コスト

BobのShopApp自前バックエンドはNode.js + PostgreSQL + S3を使用し、3人の運用チームで月額2万ドルのコスト。あるデータベースマイグレーションで4時間のダウンタイムが発生し、5万ドル分の注文を損失。リアルタイムの商品在庫同期にWebSocketが必要でしたが、自前構成では複雑で不安定でした。プッシュ通知はサードパーティサービスを使用し、頻繁に遅延が発生していました。

(2) Firebaseスイートソリューション

FirebaseはAuth + Firestore + Storage + Analytics + FCMをワンストップバックエンドとして提供 — サーバー運用不要、リアルタイムデータが自動同期。

DART
import 'package:cloud_firestore/cloud_firestore.dart';

// ⚙️ Install dependencies: flutter pub add cloud_firestore

// Custom class definition source:
// - Product: see Lesson 11 json_serializable model (add fromFirestore factory constructor)

// Firestore real-time product updates
FirebaseFirestore.instance
  .collection('products')
  .where('stock', isGreaterThan: 0)
  .snapshots()
  .listen((snapshot) {
    // UI auto-updates when stock changes
    products = snapshot.docs.map((d) => Product.fromFirestore(d)).toList();
  });
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(3) 成果:運用ゼロ + リアルタイム同期 + コスト半減

BobがFirebaseに移行後、バックエンド運用チームは2人削減され、月額コストは2万ドルから8千ドルに低下、リアルタイムデータ遅延は秒単位からミリ秒に改善しました。


3. Firebaseアーキテクチャ概要

100%
graph TD
    FB[Firebase] --> AUTH[Authentication]
    FB --> FS[Cloud Firestore]
    FB --> ST[Cloud Storage]
    FB --> AN[Analytics]
    FB --> CM[Cloud Messaging]
    FS --> |snapshots| PL[ProductList Realtime]
    AUTH --> |authStateChanges| LP[LoginPage]
    CM --> |onMessage| NP[NotificationPopup]
    AN --> |logEvent| DA[Dashboard Analytics]
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(1) Firebaseサービスマトリックス

サービス 用途 料金
Authentication ユーザー認証 寛容な無料枠
Cloud Firestore NoSQLリアルタイムデータベース 無料枠:5万読み取り/日
Cloud Storage ファイルストレージ 無料枠:5GB
Analytics ユーザー行動分析 無料
Cloud Messaging プッシュ通知 無料
Crashlytics クラッシュレポート 無料

4. Firebaseプロジェクト設定

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:FlutterFire初期化

BASH
# Install FlutterFire CLI
dart pub global activate flutterfire_cli

# Configure Firebase project
flutterfire configure

# Add dependencies
flutter pub add firebase_core firebase_auth cloud_firestore firebase_storage firebase_analytics firebase_messaging
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。
DART
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

// ⚙️ Install dependencies: flutter pub add firebase_core
// ⚙️ Project configuration: dart pub global activate flutterfire_cli && flutterfire configure

// lib/main.dart
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const ShopApp());
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

5. Authentication

(1) サインイン方式比較

方式 複雑さ ユーザー体験 ユースケース
Email/Password 低い 中程度 汎用
Googleサインイン 中程度 高い Android推奨
Appleサインイン 中程度 高い iOS必須
匿名ログイン 低い 高い(摩擦なし) 登録前のお試し
電話番号認証 高い 中程度 身元確認

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:AuthServiceラッパー

DART
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

// ⚙️ Install dependencies: flutter pub add firebase_auth google_sign_in

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Stream<User?> get authStateChanges => _auth.authStateChanges();

  Future<UserCredential> signInWithEmail(String email, String password) =>
      _auth.signInWithEmailAndPassword(email: email, password: password);

  Future<UserCredential> signUpWithEmail(String email, String password) =>
      _auth.createUserWithEmailAndPassword(email: email, password: password);

  Future<UserCredential> signInWithGoogle() async {
    final googleUser = await GoogleSignIn().signIn();
    final googleAuth = await googleUser?.authentication;
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth?.accessToken,
      idToken: googleAuth?.idToken,
    );
    return _auth.signInWithCredential(credential);
  }

  Future<UserCredential> signInAnonymously() => _auth.signInAnonymously();

  Future<void> signOut() => _auth.signOut();

  User? get currentUser => _auth.currentUser;
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:Riverpod Auth Notifier + Firebase

DART
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

// ⚙️ Install dependencies: flutter pub add firebase_auth flutter_riverpod riverpod_annotation
// ⚙️ Dev dependencies: flutter pub add --dev riverpod_generator build_runner

// Custom class definition source:
// - AuthService: see Section 5 AuthService wrapper in this lesson

@riverpod
class FirebaseAuth extends _$FirebaseAuth {
  @override
  AsyncValue<User?> build() {
    final service = AuthService();
    // Listen to auth state changes
    service.authStateChanges.listen((user) {
      state = AsyncData(user);
    });
    return AsyncData(service.currentUser);
  }

  Future<void> login(String email, String password) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => AuthService().signInWithEmail(email, password));
  }

  Future<void> loginWithGoogle() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => AuthService().signInWithGoogle());
  }

  Future<void> logout() async {
    await AuthService().signOut();
  }
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

6. Cloud Firestore

(1) データモデリング

TEXT
firestore/
├── products/{productId}
│   ├── name: "Pro Laptop"
│   ├── price: 1299.99
│   ├── stock: 150
│   ├── category: "Electronics"
│   └── imageUrl: "gs://shopapp.appspot.com/products/laptop.jpg"
├── users/{userId}
│   ├── name: "Bob"
│   ├── email: "alice@shopapp.com"
│   ├── addresses: [{...}]
│   └── preferences: {theme: "dark", currency: "USD"}
├── orders/{orderId}
│   ├── userId: "alice_123"
│   ├── items: [{productId, quantity, price}]
│   ├── total: 99.99
│   ├── status: "shipped"
│   └── createdAt: Timestamp
└── reviews/{reviewId}
    ├── productId: "laptop_001"
    ├── userId: "bob_456"
    ├── rating: 4.5
    └── comment: "Great product!"
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:Firestore CRUD

DART
import 'package:cloud_firestore/cloud_firestore.dart';

// ⚙️ Install dependencies: flutter pub add cloud_firestore

// Custom class definition source:
// - Product: see Lesson 11 json_serializable model (add fromFirestore factory constructor)

class ProductFirestore {
  final _db = FirebaseFirestore.instance;

  Future<List<Product>> getProducts({String? category, int limit = 20}) async {
    Query query = _db.collection('products').orderBy('rating', descending: true).limit(limit);
    if (category != null) query = query.where('category', isEqualTo: category);
    final snapshot = await query.get();
    return snapshot.docs.map((d) => Product.fromFirestore(d)).toList();
  }

  Future<Product> getProduct(String id) async {
    final doc = await _db.collection('products').doc(id).get();
    return Product.fromFirestore(doc);
  }

  // Real-time listener
  Stream<List<Product>> watchProducts({String? category}) {
    Query query = _db.collection('products').where('stock', isGreaterThan: 0);
    if (category != null) query = query.where('category', isEqualTo: category);
    return query.snapshots().map((s) => s.docs.map((d) => Product.fromFirestore(d)).toList());
  }

  Future<void> updateStock(String productId, int newStock) =>
      _db.collection('products').doc(productId).update({'stock': newStock});
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:Firestoreセキュリティルール

JAVASCRIPT
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Products: anyone can read, only admins can write
    match /products/{productId} {
      allow read: if true;
      allow write: if isAdmin();
    }
    // Orders: users can read/write own orders
    match /orders/{orderId} {
      allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
      allow create: if request.auth != null;
    }
    // Reviews: anyone can read, authenticated users can create
    match /reviews/{reviewId} {
      allow read: if true;
      allow create: if request.auth != null;
    }
  }
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

7. Cloud Storage

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:商品画像のアップロードとダウンロード

DART
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';

// ⚙️ Install dependencies: flutter pub add firebase_storage

class StorageService {
  final _storage = FirebaseStorage.instance;

  Future<String> uploadProductImage(String filePath, String productId) async {
    final ref = _storage.ref().child('products/$productId/${DateTime.now().millisecondsSinceEpoch}.jpg');
    final uploadTask = ref.putFile(File(filePath));
    final snapshot = await uploadTask;
    return await snapshot.ref.getDownloadURL();
  }

  Future<String> getDownloadUrl(String path) =>
      _storage.ref().child(path).getDownloadURL();

  // Upload with progress
  Stream<double> uploadWithProgress(String filePath, String productId) {
    final ref = _storage.ref().child('products/$productId/main.jpg');
    return ref.putFile(File(filePath)).snapshotEvents.map((s) {
      return s.bytesTransferred / s.totalBytes;
    });
  }
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

8. Analytics + Cloud Messaging

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:Analyticsイベントトラッキング

DART
import 'package:firebase_analytics/firebase_analytics.dart';

// ⚙️ Install dependencies: flutter pub add firebase_analytics

// Custom class definition source:
// - Product: see Lesson 11 json_serializable model
// - Order: see Lesson 14 Order model

class ShopAnalytics {
  static final _analytics = FirebaseAnalytics.instance;

  static Future<void> logViewProduct(Product product) =>
      _analytics.logEvent(name: 'view_product', parameters: {
        'product_id': product.id,
        'product_name': product.name,
        'price': product.price,
        'category': product.category,
      });

  static Future<void> logAddToCart(Product product, int quantity) =>
      _analytics.logEvent(name: 'add_to_cart', parameters: {
        'product_id': product.id,
        'quantity': quantity,
        'price': product.price,
        'currency': 'USD',
      });

  static Future<void> logPurchase(Order order) =>
      _analytics.logEvent(name: 'purchase', parameters: {
        'order_id': order.id,
        'value': order.total,
        'currency': 'USD',
        'item_count': order.items.length,
      });

  static Future<void> setUserProperty(String name, String value) =>
      _analytics.setUserProperty(name: name, value: value);
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

▶ サンプル

TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

:FCMプッシュ通知

DART
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';

// ⚙️ Install dependencies: flutter pub add firebase_messaging

class NotificationService {
  static final _messaging = FirebaseMessaging.instance;

  static Future<void> init() async {
    // Request permission
    await _messaging.requestPermission();

    // Get FCM token
    final token = await _messaging.getToken();
    debugPrint('FCM Token: $token');

    // Foreground messages
    FirebaseMessaging.onMessage.listen((message) {
      debugPrint('Foreground message: ${message.notification?.title}');
    });

    // Background messages
    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      // Handle notification tap
      final productId = message.data['product_id'];
      if (productId != null) {
        // Navigate to product detail
      }
    });
  }
}
TEXT
> 出力: ローカルのFlutter SDKで実行してください(Flutter 3.x / Dart 3.x)。PistonサーバーにはFlutterがインストールされていません — 手元のマシンで`flutter run`して動作確認してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

❓ よくある質問

Q Firebase無料枠は十分ですか?
A Firestoreは無料で5万読み取り/2万書き込み/日 — 小規模アプリには十分です。100万DAUレベルでは有料プランが必要です。
Q FirestoreとRealtime Databaseのどちらを使うべきですか?
A 新規プロジェクトではFirestoreを使用してください(より強力なクエリ、より良いデータモデル)。Realtime Databaseは非常に低遅延のリアルタイムが必要なシナリオのみです。
Q Firebase AuthトークンをバックエンドAPIとどう連携しますか?
A await user.getIdToken()でJWTを取得し、バックエンドに送信して検証します。バックエンドはFirebase Admin SDKでトークンを検証します。
Q Firestoreクエリにインデックス制限はありますか?
A 複合クエリには複合インデックスが必要です。最初の実行はインデックス作成リンクを含むエラーで失敗します。
Q FCMプッシュは中国本土で動作しますか?
A いいえ。中国本土ではベンダー固有プッシュ(Huawei/Xiaomi/OPPO/VIVO)またはJPushのようなサードパーティサービスの統合が必要です。
Q Firestoreセキュリティルールはどうデバッグしますか?
A FirebaseコンソールのRules Playgroundで読み取り/書き込みリクエストをシミュレートしてルールをテストできます。本番では必ず厳格なルールを設定してください。

📖 まとめ


📝 練習問題

  1. 基本 (⭐):Firebaseプロジェクトを設定し、Email/Passwordの登録とログインを実装し、authStateChangesでユーザー状態を表示してください。
  2. 中級 (⭐⭐):Firestore商品CRUD + リアルタイムリスニングを実装し、商品在庫が変更されたときにUIが自動更新されるようにしてください。
  3. チャレンジ (⭐⭐⭐):完全なFirebase統合を実装してください:Googleサインイン + Firestoreリアルタイム商品 + Storage画像アップロード(進捗付き)+ Analyticsイベントトラッキング + FCMプッシュ通知。

← 前のレッスン | 次のレッスン →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%