【Flutter】Cloud Firestoreと連携

flutter

1. 概要

FlutterとCloud Firestoreを連携し、データ管理をする内容となります。

対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。

2. プロジェクトの準備

2-1. プロジェクトを作成

※未だNodeがインストールされてない場合はこちらを参考

3. Firestoreのセットアップ

こちらを参考

4. プロジェクトのロケーションを変更

こちらを参考

5. firebase-toolsのインストール

npm install -g firebase-tools

firebase --version

6. FlutterFireの初期化

6-1. flutterfire_cli

dart pub global activate flutterfire_cli

6-2. configure

  • 対象のFirebaseのプロジェクトを選択
flutterfire configure

※flutterプロジェクトに「lib/firebase_options.dart」が生成される。

7. Dependenciesの追加

7-1. FlutterからFirebaseへの接続

flutter pub add firebase_core

7-2. Cloud Firestore Plugin for Flutter

flutter pub add cloud_firestore

8. ファイルの修正

8-1. 「android/local.properties」に一行追記

flutter.minSdkVersion=23

8-2. 「android/app/build.gradle」を修正

    defaultConfig {
        applicationId "com.example.firestore_demo"
        // minSdkVersion flutter.minSdkVersion // 修正前
        minSdkVersion localProperties.getProperty('flutter.minSdkVersion') // 修正後
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

9. データ構造

{
  users: {
    {userId}: {
      "userId": string,
      "name": string,
      "createdAt": timestamp,
      bookings: {
        {bookingId}: {
          "place": string,
          "price": int,
          "createdAt": timestamp
        }
      }
    }
  }
}

10. ソースコード

10-1. lib/firebase_options.dart

自動生成

10-2. lib/models/booking.dart

import 'package:cloud_firestore/cloud_firestore.dart';

class Booking {
  final String place;
  final int price;
  final DateTime createdAt;

  Booking({required this.place, required this.price, required this.createdAt});

  factory Booking.fromFirestore(DocumentSnapshot<Map<String, dynamic>> snapshot,
      SnapshotOptions? options) {
    final data = snapshot.data();
    return Booking(
        place: data?['place'],
        price: data?['price'],
        createdAt: data?['createdAt'].toDate());
  }

  Map<String, dynamic> toFirestore() {
    return {
      "place": place,
      "price": price,
      "createdAt": createdAt,
    };
  }
}

10-3. lib/db/firestore_helper.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_demo/models/booking.dart';

class FirestoreHelper {
  FirestoreHelper._getInstance();
  static final FirestoreHelper instance = FirestoreHelper._getInstance();

  final _db = FirebaseFirestore.instance;

  static const _collectionUsers = 'users';
  static const _subCollectionBookings = 'bookings';

  Future<List<DocumentSnapshot>> selectAllBookings(String userId) async {
    final collectionRef = _db
        .collection(_collectionUsers)
        .doc(userId)
        .collection(_subCollectionBookings)
        .withConverter(
          fromFirestore: Booking.fromFirestore,
          toFirestore: (Booking bookings, _) => bookings.toFirestore(),
        );
    final collectionSnap = await collectionRef.get();
    return collectionSnap.docs;
  }

  Future<Booking?> selectBooking(String userId, String place) async {
    final docRef = _db
        .collection(_collectionUsers)
        .doc(userId)
        .collection(_subCollectionBookings)
        .doc(place)
        .withConverter(
            fromFirestore: Booking.fromFirestore,
            toFirestore: (Booking booking, _) => booking.toFirestore());
    final docSnap = await docRef.get();
    return docSnap.data();
  }

  Future<void> insert(Booking booking, String userId) async {
    final docRef = _db
        .collection(_collectionUsers)
        .doc(userId)
        .collection(_subCollectionBookings)
        .doc(booking.place)
        .withConverter(
            fromFirestore: Booking.fromFirestore,
            toFirestore: (Booking booking, _) => booking.toFirestore());
    return await docRef.set(booking);
  }

  Future<void> delete(String userId, String docId) async {
    return await _db
        .collection(_collectionUsers)
        .doc(userId)
        .collection(_subCollectionBookings)
        .doc(docId)
        .delete();
  }
}

10-4. lib/views/home.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_demo/db/firestore_helper.dart';
import 'package:firestore_demo/models/booking.dart';
import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final String _userId = 'test';
  final TextEditingController _controllerPlace = TextEditingController();
  final TextEditingController _controllerPrice = TextEditingController();
  List<Booking> _bookingList = [];

  @override
  void initState() {
    super.initState();
    _getBookingList();
  }

  @override
  void dispose() {
    _controllerPlace.dispose();
    _controllerPrice.dispose();
    super.dispose();
  }

  List<Booking> _bookingListFromDocToList(
      List<DocumentSnapshot> bookingSnapshot) {
    return bookingSnapshot
        .map((doc) => Booking(
            place: doc['place'],
            price: doc['price'],
            createdAt: doc['createdAt'].toDate()))
        .toList();
  }

  void _getBookingList() async {
    List<DocumentSnapshot> bookingSnapshot =
        await FirestoreHelper.instance.selectAllBookings(_userId);
    _bookingList = _bookingListFromDocToList(bookingSnapshot);
    setState(() {});
  }

  void _addBooking(String place, String price) async {
    final Booking booking = Booking(
        place: place, price: int.parse(price), createdAt: DateTime.now());
    await FirestoreHelper.instance.insert(booking, _userId);
    _bookingList.add(booking);
    setState(() {});
  }

  void _deleteBooking(String docId, int index) async {
    await FirestoreHelper.instance.delete(_userId, docId);
    _bookingList.removeAt(index);
    setState(() {});
  }

  void _getBooking(String docId) async {
    Booking? booking =
        await FirestoreHelper.instance.selectBooking(_userId, docId);
    if (booking != null) {
      // ignore: use_build_context_synchronously
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text('${booking.place} : ${booking.price}'),
        duration: const Duration(seconds: 3),
      ));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Expanded(
              child: SizedBox(
                child: _bookingList.isEmpty
                    ? const Center(child: Text('NO DATA'))
                    : ListView.builder(
                        itemCount: _bookingList.length,
                        itemBuilder: (BuildContext context, int index) {
                          final booking = _bookingList[index];
                          return Card(
                            child: InkWell(
                              child: Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Row(
                                  children: <Widget>[
                                    Expanded(
                                      child: Text(
                                        booking.place,
                                        style: const TextStyle(fontSize: 20),
                                      ),
                                    ),
                                    Expanded(
                                      child: Text(
                                        booking.price.toString(),
                                        style: const TextStyle(fontSize: 20),
                                      ),
                                    ),
                                    SizedBox(
                                      width: 50,
                                      height: 25,
                                      child: ElevatedButton(
                                          onPressed: () {
                                            _getBooking(booking.place);
                                          },
                                          child: const Icon(
                                            Icons.book,
                                            size: 15,
                                          )),
                                    ),
                                    const SizedBox(
                                      width: 5,
                                      height: 25,
                                    ),
                                    SizedBox(
                                      width: 50,
                                      height: 25,
                                      child: ElevatedButton(
                                          onPressed: () {
                                            _deleteBooking(
                                                booking.place, index);
                                          },
                                          child: const Icon(
                                            Icons.delete,
                                            size: 15,
                                          )),
                                    ),
                                  ],
                                ),
                              ),
                            ),
                          );
                        }),
              ),
            ),
            Row(
              children: [
                Expanded(
                    child: TextField(
                  controller: _controllerPlace,
                  autofocus: false,
                  decoration: const InputDecoration(labelText: 'Tokyo'),
                )),
                Expanded(
                    child: TextField(
                  controller: _controllerPrice,
                  autofocus: false,
                  decoration: const InputDecoration(labelText: '10000'),
                  onSubmitted: (String value) {
                    _addBooking(_controllerPlace.text, value);
                    _controllerPlace.clear();
                    _controllerPrice.clear();
                  },
                )),
                ElevatedButton(
                    onPressed: () {
                      _addBooking(_controllerPlace.text, _controllerPrice.text);
                      _controllerPlace.clear();
                      _controllerPrice.clear();
                    },
                    child: const Icon(Icons.add)),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

10-5. lib/main.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:firestore_demo/firebase_options.dart';
import 'package:firestore_demo/views/home.dart';
import 'package:flutter/material.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bookings',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'My bookings'),
    );
  }
}

11. 結果

12. 備考

FlutterからFirestoreにアクセスしてデータのやり取りをしてみました。

  • 登録
  • 一覧取得
  • 1件取得
  • 削除

13. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. flutter

    【Flutter】アプリにAdmobバナー広告を設置

  2. Firebase

    【Firebase】Cloud Firestoreを使ってみる

  3. flutter

    【Flutter】Flutter実装メモ showModalBotto…

  4. flutter

    Flutterのすゝめ

  5. flutter

    【Flutter】Firebase Dynamic Linksを使う

  6. flutter

    【Flutter】コード自動生成とGraphQLClientを使ってG…

最近の記事

  1. AWS
  2. AWS

制作実績一覧

  1. Checkeys