Flutter と Firebase Firestore を使ったリアルタイムデータの保存・同期【初心者向け】

Flutter

はじめに

Flutterアプリケーションでリアルタイムにデータを保存・同期させるには、Firebase Firestoreが非常に便利です。Firestoreはクラウドデータベースで、データの追加、削除、更新がリアルタイムで反映され、複数のユーザーが同時に利用するアプリケーションに最適です。

この記事では、FlutterアプリでFirestoreを使ってリアルタイムデータを保存・同期する方法を解説します。Firestoreの基本的な操作や、実際にFlutterと連携してリアルタイムにデータを扱う方法を、初心者でも理解できるように丁寧に説明します。


Firebase Firestore のセットアップ

Firebase プロジェクトの作成

まずは、Firebaseにプロジェクトを作成して、Firestoreを有効にします。

Firebaseプロジェクトの作成・プロジェクトの初期化はこちらの記事で説明しています。
Firebase Authenticationのセットアップの手順後に続きを実施してください
(Firebase Authenticationを有効にする必要はありません)

Flutter Firebase Authentication の導入【初心者向け】
Firebase Authenticationを使ってFlutterアプリにGoogleサインインやメール認証を導入する方法を解説します。
  1. プロジェクトを作成したら、Firestore Database(Cloud Firestore)を有効化します。
  2. その後、Firestoreデータベースのモードを「テストモード」に設定します。これにより、初期開発段階で誰でもデータにアクセスできるようになります。

FlutterアプリにFirebaseを追加

次に、FlutterアプリケーションにFirebaseを追加します。

  • pubspec.yamlファイルに以下のパッケージを追加します。(バージョンは適宜変更してください)
YAML
dependencies:
  firebase_core: ^3.6.0
  cloud_firestore: ^5.4.4
  • 以下のコマンドを実行してパッケージをインストールします。
Bash
flutter pub get

Firestore を使ったデータの保存

Firestoreにデータを保存するためには、Firestoreのインスタンスを使ってコレクションにドキュメントを追加します。

データの追加

cloud_firestoreパッケージを使い、Firestoreにデータを保存する基本的なコードは以下の通りです。

  • collection('todos') は、Firestoreのtodosという名前のコレクション(テーブルのようなもの)にアクセスしています。存在しない場合は自動的に作成されます。
  • add({...}) メソッドは、todosコレクションに新しいドキュメントを追加するために使用します。
Dart
import 'package:cloud_firestore/cloud_firestore.dart';

  Future<void> addTodoItem(String task) async {
    await FirebaseFirestore.instance.collection('todos').add({
      'task': task,
      'created_at': Timestamp.now(),
      'is_completed': false,
    });
  }

データの更新

Firestoreに保存されたデータを更新するには、updateメソッドを使います。

  • doc(id): 特定のドキュメント(タスク)を指定するためのメソッドです。ここでは、idで指定されたドキュメント(タスク)を取得します。
  • update({...}): 既存のドキュメントのデータを更新します。ここでは、is_completedフィールドを、関数に渡されたisCompletedの値に更新しています。
Dart
  // タスクの更新(Update)
  Future<void> updateTodoItem(String id, bool isCompleted) async {
    await FirebaseFirestore.instance.collection('todos').doc(id).update({
      'is_completed': isCompleted,
    });
  }

データの削除

データを削除する場合は、deleteメソッドを使用します。

Dart
  // タスクの削除(Delete)
  Future<void> deleteTodoItem(String id) async {
    await FirebaseFirestore.instance.collection('todos').doc(id).delete();
  }

Firestore のリアルタイム同期

Firestoreの大きな特徴は、データがリアルタイムに同期される点です。FirestoreのStreamを使って、データが変更されたときにリアルタイムで画面に反映する仕組みを実装します。

リアルタイムデータの取得

StreamBuilderを使って、Firestore内のデータが変更された際に、即座にアプリに反映させる方法を紹介します。

  • StreamBuilder:ストリームを監視し、データが更新されるたびに自動でUIを再構築します。Firestoreのリアルタイムのデータを取得するために使います。
  • stream:FirebaseFirestore.instance.collection('todos').orderBy('created_at').snapshots() は、Firestoreのtodosコレクションをcreated_atフィールドで並べ替え、リアルタイムのスナップショットを取得します。これにより、タスクの追加や変更があればすぐに反映されます。
  • snapshot: Firestoreから取得されたデータ(QuerySnapshot)がsnapshotに渡されます。このスナップショットには、現在のドキュメントの状態(タスクのリスト)が含まれています。
Dart
// Firestoreからタスクのリストを取得(Read)
  Widget buildTodoList() {
    return StreamBuilder(
      stream: FirebaseFirestore.instance.collection('todos').orderBy('created_at').snapshots(),
      builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (!snapshot.hasData) {
          return Center(child: CircularProgressIndicator());
        }
        var todos = snapshot.data!.docs;
        return ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, index) {
            var todo = todos[index];
            return ListTile(
              title: Text(todo['task']),
              trailing: Checkbox(
                value: todo['is_completed'],
                onChanged: (value) {
                  updateTodoItem(todo.id, value!);  // タスクの更新(Update)
                },
              ),
              onLongPress: () {
                deleteTodoItem(todo.id);  // タスクの削除(Delete)
              },
            );
          },
        );
      },
    );
  }

このStreamBuilderでは、Firestore内のtodosコレクションに変更があった際、即座にデータが取得され、リストビューに反映されます。


完成系のコード

今回作成したコードの完成系です。

Dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(); // Firebase初期化
  runApp(TodoApp());
}

class TodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firestore Todo App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListScreen(),
    );
  }
}

class TodoListScreen extends StatefulWidget {
  @override
  _TodoListScreenState createState() => _TodoListScreenState();
}

class _TodoListScreenState extends State<TodoListScreen> {
  // タスクの追加(Create)
  Future<void> addTodoItem(String task) async {
    await FirebaseFirestore.instance.collection('todos').add({
      'task': task,
      'created_at': Timestamp.now(),
      'is_completed': false,
    });
  }

  // タスクの更新(Update)
  Future<void> updateTodoItem(String id, bool isCompleted) async {
    await FirebaseFirestore.instance.collection('todos').doc(id).update({
      'is_completed': isCompleted,
    });
  }

  // タスクの削除(Delete)
  Future<void> deleteTodoItem(String id) async {
    await FirebaseFirestore.instance.collection('todos').doc(id).delete();
  }

  // Firestoreからタスクのリストを取得(Read)
  Widget buildTodoList() {
    return StreamBuilder(
      stream: FirebaseFirestore.instance.collection('todos').orderBy('created_at').snapshots(),
      builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (!snapshot.hasData) {
          return Center(child: CircularProgressIndicator());
        }
        var todos = snapshot.data!.docs;
        return ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, index) {
            var todo = todos[index];
            return ListTile(
              title: Text(todo['task']),
              trailing: Checkbox(
                value: todo['is_completed'],
                onChanged: (value) {
                  updateTodoItem(todo.id, value!);  // タスクの更新(Update)
                },
              ),
              onLongPress: () {
                deleteTodoItem(todo.id);  // タスクの削除(Delete)
              },
            );
          },
        );
      },
    );
  }

  // モーダルを表示してタスクを追加
  void _showAddTodoDialog() {
    TextEditingController _taskController = TextEditingController();

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text('Add a new task'),
          content: TextField(
            controller: _taskController,
            decoration: InputDecoration(hintText: 'Enter task here'),
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();  // モーダルを閉じる
              },
              child: Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                if (_taskController.text.isNotEmpty) {
                  addTodoItem(_taskController.text);  // タスクを追加
                  Navigator.of(context).pop();  // モーダルを閉じる
                }
              },
              child: Text('Add'),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Firestore Todo List')),
      body: buildTodoList(),  // タスクのリスト表示
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddTodoDialog,  // モーダルを表示してタスクを追加
        child: Icon(Icons.add),
      ),
    );
  }
}

まとめ

この記事では、FlutterとFirebase Firestoreを使ったリアルタイムデータの保存・同期について解説しました。Firestoreは、データがリアルタイムに同期されるため、複数のユーザーが同時に使うアプリケーションや、データの即時反映が求められるアプリに最適です。

この記事のポイント

  • Firestoreのセットアップ方法とFlutterとの連携。
  • データの保存、更新、削除の基本操作。
  • StreamBuilderを使ったリアルタイム同期の実装。

これらを活用して、よりインタラクティブでリアルタイム性の高いアプリを作成してみてください。

コメント

タイトルとURLをコピーしました