1. 概要
FlutterとCloud Firestoreを連携し、データ管理をする内容となります。
対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。
2. プロジェクトの準備
2-1. プロジェクトを作成
- Flutter
- Firebase
※未だ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. 参考
- FlutterFire Overview | FlutterFire
- Cloud Firestore | FlutterFire
- cloud_firestore | Flutter Package (pub.dev)
- CloudFirestoreでデータを取得する | Firebase (google.com)
投稿者プロフィール

-
開発好きなシステムエンジニアです。
卓球にハマってます。
最新の投稿
【Next.js】2025年3月8日【NextJS】Cropping a portion of an image with React Cropper
【Next.js】2025年2月9日【NextJS】View and Download PDF
【AWS】2025年2月1日【AWS】Github ActionsやAWS SAMを使ってAWS S3・CloudFrontにウェブコンテンツをデプロイし、サブドメインにアクセスできるようにする
【AWS】2025年1月25日【AWS】Deploy Serverless NextJS app with AWS Lambda Web Adapter using AWS SAM