📆
table_calendar + cloud_firestore
対象者
- Flutterでカレンダーアプリを作りたい
- Cloud Firestoreと組み合わせたい
- 動くもの見てみたい
公式のコードを参考に作ってみました。
こんなものを作りました。データが保存されている日には、黒丸がつきます。日付をタップすると保存されているデータを確認することができます。
プロジェクトの説明
とある人が、健康管理アプリを作ってみたいと言っていた。カレンダーアプリを作るときは、Map<Key, Value>、List<T>の知識が必要だったりします。チュートリアルを何度かやりましたが難しかったです😅
まずはこんな感じで、Cloud Firestoreにダミーデータを作っておいてください。
コード綺麗ではないですが💦
モデルクラスと、Firestoreからデータを全て取得するメソッド、時間を扱うロジックがあるコードを作成します。
- やること
- データを全て取得
- for in でループする
- Mapに、
.addAll
で追加する。
import 'dart:collection';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:table_calendar/table_calendar.dart';
class Event {
final String? uuid;
final String? memo;
final Timestamp? createdAt;
// toJson
Map<String, dynamic> toJson() => {
'uuid': uuid,
'memo': memo,
'createdAt': createdAt,
};
// fromJson
Event.fromJson(Map<String, dynamic> json)
: uuid = json['uuid'] as String?,
memo = json['memo'] as String?,
createdAt = json['createdAt'] as Timestamp?;
}
class EventUtils {
final db = FirebaseFirestore.instance;
// event collectionから全データを取得
Future<List<Event>> getEvents() async {
final snapshot = await db.collection('event').get();
return snapshot.docs.map((doc) => Event.fromJson(doc.data())).toList();
}
// event collectionから全データを取得し、日付ごとにグループ化
Future<LinkedHashMap<DateTime, List<Event>>> fetchEvents() async {
// event collectionから全データを取得
final events = await getEvents();
// eventMapに日付ごとにグループ化
final eventMap = <DateTime, List<Event>>{};
// for文でeventsを回して、eventMapに日付ごとにグループ化
for (final event in events) {
// event.createdAtをDateTime型に変換
final eventDate = event.createdAt!.toDate();
// eventMapに日付ごとにグループ化
if (eventMap[eventDate] == null) {
// eventMapにeventDateがない場合は、[event]をリストで追加
eventMap[eventDate] = [event];
} else {
// eventMapにeventDateがある場合は、.add(event)でリストに追加
eventMap[eventDate]!.add(event);
}
}
// LinkedHashMap<DateTime, List<Event>>を返す
return LinkedHashMap<DateTime, List<Event>>(
equals: isSameDay,
hashCode: getHashCode,
)..addAll(eventMap); // eventMapを返す
}
}
int getHashCode(DateTime key) {
return key.day * 1000000 + key.month * 10000 + key.year;
}
List<DateTime> daysInRange(DateTime first, DateTime last) {
final dayCount = last.difference(first).inDays + 1;
return List.generate(
dayCount,
(index) => DateTime.utc(first.year, first.month, first.day + index),
);
}
final kToday = DateTime.now();
final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
カレンダーのUIのロジック
日付をタップするイベントがあるので、日付が青く点灯したり、リストに値が表示されます。複雑なことをしているので、これはおそらくStatefulWidget
でないと難しいそう。
import 'package:flutter/material.dart';
import 'package:flutter_table_calendart/utils/event_utils.dart';
import 'package:table_calendar/table_calendar.dart';
class CalendarScreen extends StatefulWidget {
const CalendarScreen({super.key});
_CalendarScreenState createState() => _CalendarScreenState();
}
class _CalendarScreenState extends State<CalendarScreen> {
// カレンダーの最初の日付
Map<DateTime, List<Event>> _events = {};
// カレンダーの最後の日付
List<Event> _selectedEvents = [];
// 選択された日付
DateTime _selectedDay = DateTime.now();
void initState() {
super.initState();
// _fetchEventsを呼び出す
_fetchEvents();
}
// イベントを取得する
Future<void> _fetchEvents() async {
try {
// EventUtilsをインスタンス化
final eventUtils = EventUtils();
// fetchEventsを呼び出し、取得したデータをfetchedEventsに代入
final fetchedEvents = await eventUtils.fetchEvents();
setState(() {
// _eventsにfetchedEventsを代入
_events = fetchedEvents;
// _selectedEventsに_events[_selectedDay]を代入
// _selectedDayが_eventsにない場合は、[]を代入
// _events[] のデータ型はList<Event>なので、List<Event>を代入
_selectedEvents = _events[_selectedDay] ?? [];
});
} catch (e) {
debugPrint('Failed to fetch events: $e');
}
}
void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
setState(() {
// _selectedDayにselectedDayを代入
_selectedDay = selectedDay;
// _selectedEventsに_events[_selectedDay]を代入
// _selectedDayが_eventsにない場合は、[]を代入
// _events[] のデータ型はList<Event>なので、List<Event>を代入
_selectedEvents = _events[_selectedDay] ?? [];
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar'),
),
body: Column(
children: [
TableCalendar(
// _eventsのキーを取得
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
firstDay: kFirstDay, // カレンダーの最初の日付
lastDay: kLastDay, // カレンダーの最後の日付
focusedDay: DateTime.now(), // フォーカスされている日付
eventLoader: (day) => _events[day] ?? [], // イベントを取得
onDaySelected: _onDaySelected, // 日付が選択されたときの処理
),
// 選択された日付のイベントを表示
Expanded(
child: ListView.builder(
itemCount: _selectedEvents.length, // _selectedEventsの数だけリストを表示
itemBuilder: (context, index) {
final event =
_selectedEvents[index]; // _selectedEventsのindex番目のデータを取得
return ListTile(
title: Text(event.memo ?? ''),
);
},
),
),
],
),
);
}
}
main.dart
でインポートして実行すればデモアプリを動かせます。
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_table_calendart/event_example/calendar_screen.dart';
import 'firebase_options.dart';
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(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const CalendarScreen(),
);
}
}
感想
カレンダーアプリを作ってみましたが、ロジックを考えるのは結構難しいです。ダミーのデータを使うだけでもDartのロジックを考えるので、詰まりました。カレンダーを使うとなると、HashMapだとか文法の知識がいるわけですが、パッケージのコードを使用する前提でやるときは、独特な文法を使うので、もっとハードル上がりました💦
カレンダー作るときは、Map, List, forを使う場面が多かったので、アルゴリズムへの深い理解が求められそうと思いました。
参考にしたもの
Discussion