📆
table_calendarとFirestoreでアプリを作ってみた
日付ごとにデータを保存する機能を作る
table_calendarとローカルDBでメモアプリを作ってみたが、FireStoreでもやって見たいと思い作って見ました。
Flutter Webだとこんな感じですね
必要なパッケージを追加する
これだけあればカレンダーアプリが作れます。
追加・表示・削除をするページ
複雑なことをしなければ多くのコードを書かなくてもカレンダーアプリを作れました。
このようになっております。
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
class CalendarPage extends StatefulWidget {
const CalendarPage({Key? key}) : super(key: key);
State<CalendarPage> createState() => _CalendarPageState();
}
class _CalendarPageState extends State<CalendarPage> {
final CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false, // キーボードが出てきても画面が崩れないようにする
appBar: AppBar(
title: const Text('Calendar'),
actions: [
IconButton(
onPressed: () async {
try {
// ダイアログを出して入力する
final result = await showDialog<String>(
context: context,
builder: (context) {
final TextEditingController controller =
TextEditingController();
return AlertDialog(
title: const Text('メモを入力してください'),
content: TextField(
controller: controller,
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('キャンセル'),
),
TextButton(
onPressed: () async {
await FirebaseFirestore.instance
.collection('calendar')
.add({
'date': Timestamp.fromDate(_selectedDay!),
'memo': controller.text,
});
if (mounted) {
Navigator.pop(context);
}
},
child: const Text('OK'),
),
],
);
},
);
} catch (e) {
print("Error adding document: $e");
}
},
icon: const Icon(Icons.add),
),
],
),
body: Column(
children: [
TableCalendar(
focusedDay: _focusedDay, // どの日付を選択したか
firstDay: DateTime(1990), // 最初に利用可能な日付
lastDay: DateTime(2050), // 最後に利用可能な日付
calendarFormat: _calendarFormat,
// カレンダーウィジェットに以下のコードを追加すると、ユーザーのタップに反応し、
// タップされた日を選択されたようにマークします
selectedDayPredicate: (day) =>
isSameDay(_selectedDay, day), // 選択された日付をマークする
onDaySelected: (selectedDay, focusedDay) {
// 日付が選択されたときに呼び出される
_focusedDay = focusedDay;
_selectedDay = selectedDay;
setState(() {});
},
),
// StreamBuilderを使って、Firestoreのcalendarコレクションのデータを取得する
StreamBuilder<QuerySnapshot>(
stream:
FirebaseFirestore.instance.collection('calendar').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// calendarコレクションのデータを取得する
final List<QueryDocumentSnapshot> documents =
snapshot.data!.docs;
// 選択された日付と一致するドキュメントだけをフィルタリング
final List<QueryDocumentSnapshot> filteredDocuments =
documents.where((doc) {
final date = (doc['date'] as Timestamp).toDate();
return isSameDay(_selectedDay, date);
}).toList();
// calendarコレクションのデータを日付順に並び替える
filteredDocuments
.sort((a, b) => a['date'].compareTo(b['date']));
return Expanded(
child: ListView.builder(
itemCount: filteredDocuments.length,
itemBuilder: (context, index) {
// calendarコレクションのデータを取得する
final document = filteredDocuments[index];
// calendarコレクションのデータをDateTime型に変換する
final date = (document['date'] as Timestamp).toDate();
// calendarコレクションのデータを表示する
return ListTile(
trailing: IconButton(
onPressed: () async {
await FirebaseFirestore.instance
.collection('calendar')
.doc(document.id)
.delete();
},
icon: const Icon(Icons.delete),
),
title: Text(document['memo']),
subtitle:
Text('${date.year}/${date.month}/${date.day}'),
);
},
),
);
}
return const Center(child: CircularProgressIndicator());
},
),
],
),
);
}
}
main.dartでimportして実行する
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:kenty_app/calendar/calender_page.dart';
import 'package:kenty_app/firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const CalendarPage()
);
}
}
iOSで操作するとこんな感じです
これはまだ削除機能つけてない時に撮った写真です。削除機能は追加してます。
カレンダーのUI
日付をタップして、日にちを選択する。
日付ごとに保存されているデータを表示できます。
AppBar右上の + ボタンを押すとダイアログが出てきて、追加ができます。
データはこんな感じで追加されてます。
最後に
簡単だけど、難しいカレンダーアプリを作って見ました。以前作成した別のコードもご紹介しておきます。
日続けのところにデータの数だけ黒いまるが出てきます。
import 'dart:collection';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Map<DateTime, List<dynamic>> _eventsList;
late DateTime _focused;
DateTime? _selected;
late CollectionReference _collectionRef;
void initState() {
super.initState();
_selected = DateTime.now();
_focused = DateTime.now();
_eventsList = {};
_collectionRef = FirebaseFirestore.instance.collection('events');
_collectionRef.snapshots().listen((snapshot) {
setState(() {
_eventsList = {};
snapshot.docs.forEach((doc) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
DateTime date = (data['date'] as Timestamp).toDate();
String title = data['title'];
if (_eventsList[date] == null) {
_eventsList[date] = [title];
} else {
_eventsList[date]!.add(title);
}
});
});
});
}
int getHashCode(DateTime key) {
return key.day * 1000000 + key.month * 10000 + key.year;
}
Widget build(BuildContext context) {
final _events = LinkedHashMap<DateTime, List<dynamic>>(
equals: isSameDay,
hashCode: getHashCode,
)..addAll(_eventsList);
List<dynamic> getEvent(DateTime day) {
return _events[day] ?? [];
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
TableCalendar(
firstDay: DateTime.utc(2022, 4, 1),
lastDay: DateTime.utc(2025, 12, 31),
eventLoader: getEvent,
selectedDayPredicate: (day) {
return isSameDay(_selected!, day);
},
onDaySelected: (selected, focused) {
if (!isSameDay(_selected!, selected)) {
setState(() {
_selected = selected;
_focused = focused;
});
}
},
focusedDay: _focused,
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: getEvent(_selected!).length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(getEvent(_selected!)[index]),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_showAddDialog(context);
},
child: Icon(Icons.add),
),
);
}
Future<void> _showAddDialog(BuildContext context) async {
String? title = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Add Event'),
content: TextField(),
actions: <Widget>[
TextButton(
child: Text('CANCEL'),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: Text('ADD'),
onPressed: () {
Navigator.pop(context, 'New Event');
},
),
],
);
},
);
if (title != null) {
setState(() {
if (_eventsList[_selected!] == null) {
_eventsList[_selected!] = [title];
} else {
_eventsList[_selected!]!.add(title);
}
});
await _collectionRef.add({
'date': _selected,
'title': title,
'created_at': Timestamp.now(),
});
}
}
}
Discussion