Flutterでカレンダーにアクセスする方法

# はじめに
<!-- 発端や概要を記載 -->
Flutterでデバイス標準のカレンダーにアクセスする方法について調べましたので共有します。
サンプルとなるプロジェクトを[Github](https://github.com/AppDeveloperMLLB/device_calendar_sample)に作成しましたので、UIも含めたサンプルが欲しい方のご参考になれば幸いです。
(画面遷移に`go_router`と`riverpod`を使っているので、初学者の方には全体像が若干ややこしく感じるかもしれませんが、サンプルプロジェクトの`device_calendar_page.dart`と`add_event_page.dart`を見ていただければ、内容わかりやすいかと思います)

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [カレンダーにアクセスできるパッケージ](#カレンダーにアクセスできるパッケージ)
1. [設定](#設定)
1. [カレンダーの取得](#カレンダーの取得)
1. [カレンダーにイベントを登録](#カレンダーにイベントを登録)
1. [さいごに](#さいごに)
1. [参考文献](#reference)

<!-- 各チャプター -->
<a id="#カレンダーにアクセスできるパッケージ"></a>
# カレンダーにアクセスできるパッケージ
[device_calendar](https://pub.dev/packages/device_calendar)というパッケージを使えば**簡単にデバイスのカレンダーへとアクセスできます。**
使う際の設定も、AndroidならAndroidManifest.xml、iOSならInfo.plistに**設定を数行追加するだけ**です。

<a id="#設定"></a>
# 設定
### 共通の設定
まずpubspec.yamlに`device_calendar`を追加して、pub getしてください。

### Androidの場合
AndroidManifest.xmlに以下を追加してください
```
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
```

追加する場所は、manifestタグのすぐ下でOKです。
``` AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.xxxxxx">
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
    ・
    ・
```

### iOSの場合
Info.plistに以下を追加します
```
<key>NSCalendarsUsageDescription</key>
<string>Access most functions for calendar viewing and editing.</string>
<key>NSContactsUsageDescription</key>
<string>Access contacts for event attendee editing.</string>
```
<a id="#カレンダーの取得"></a>
# カレンダーの取得
`device_calendar`を使用すると、スマホ内のすべてのカレンダーの取得ができ、それぞれに読み書きできるようになります。
本記事では、説明簡略化のため、**スマホでデフォルトの設定となっているカレンダーのみに絞り、カレンダーにイベントを登録
してみたい**と思います。
デフォルトのカレンダーを取得するメソッドは以下となります。
(メソッドの最後で、`_defaultCalendar`に代入しているのはクラス内の他の場所で変数として使用したかったからです)
```dart
  Future<Calendar> _getDefaultCalender() async {
      var permissionsGranted = await _deviceCalendarPlugin.hasPermissions();
      if (permissionsGranted.isSuccess && !permissionsGranted.data!) {
        permissionsGranted = await _deviceCalendarPlugin.requestPermissions();
        if (!permissionsGranted.isSuccess || !permissionsGranted.data!) {
          throw Exception("Not granted access to your calendar");
        }
      }

      final calendarsResult = await _deviceCalendarPlugin.retrieveCalendars();
      final calendars = calendarsResult?.data;
      if(calendars == null || calendars.isEmpty) {
        throw Exception("Can not get calendars");
      }

      _defaultCalendar = calendars!
        .firstWhere((element) => element.isDefault ?? false);
      return _defaultCalendar!;
  }
```


コードの解説を行います。
まず、スマホ内のカレンダーは以下の部分で取得しています。
```dart
final calendarsResult = await _deviceCalendarPlugin.retrieveCalendars();
final calendars = calendarsResult?.data;
```

つづいて、
カレンダーは上記で取得可能なのですが、取得の前にカレンダーのアクセス許可があるか確認し、なければ許可を得る必要があります。
そのためのコードが以下です。

```dart
var permissionsGranted = await _deviceCalendarPlugin.hasPermissions();
if (permissionsGranted.isSuccess && !permissionsGranted.data!) {
  permissionsGranted = await _deviceCalendarPlugin.requestPermissions();
  if (!permissionsGranted.isSuccess || !permissionsGranted.data!) {
    throw Exception("Not granted access to your calendar");
  }
}
```


そして、最後に取得したカレンダーには`isDefault`というデフォルト設定のカレンダーかどうかを判定するプロパティがあるのでそのプロパティを元にデフォルトのカレンダーを取得しています。
```dart
_defaultCalendar = calendars!.firstWhere((element) => element.isDefault ?? false);
```

## カレンダーの取得に失敗する場合
Emulatorを使用している場合で、カレンダーを使用したことがない場合、取得に失敗します。
Emulatorからカレンダーアプリを開いて、ログインしてからコードを実行してください。

<a id="#カレンダーにイベントを登録"></a>
# カレンダーにイベントを登録
カレンダーにイベントを登録するメソッドは以下となります。
**(自分の環境では実際にカレンダーに表示されるまで時間がかかりました(1～2分くらい？))**
```dart
  Future<void> _addEvent() async{
    final event = Event(
      widget.defaultCalenderId,
      title: _titleController.text,
      start: TZDateTime.local(2022, 9, 28, 2),
      end: TZDateTime.local(2022, 9, 28, 3),
    );
    final result = await DeviceCalendarPlugin().createOrUpdateEvent(event);
    if(result == null) {
      return;
    }
    if(result.isSuccess){
      return;
    }

    if(!result.hasErrors){
      return;
    }

    throw Exception(result.errors.join());
  }
```

**端的に説明すると、`Event`インスタンスを作成し、`DeviceCalendarPlugin().createOrUpdateEvent`メソッドの引数に渡すだけ**です。
私もすべて把握できていませんが、**`Event`クラスには場所やリマインダー、終日、出席者などといった設定もできる**ようです。
GoogleカレンダーやiOSのカレンダーをイメージしていただくとわかりやすいかと思います。

<a id="#さいごに"></a>
# さいごに
`DeviceCalendarPlugin`のメソッドを見ると、カレンダーの追加や、カレンダーに登録したイベントの取得や編集、削除といったことも行えそうです。大変便利なパッケージなので、カレンダーを実装する際は、一からカレンダーの作成をするのも一つの手段ですが、**スマホに入っているデフォルトのカレンダーを使用するのもアリだと思いました。**

# 参考文献
- [Using Device_Calendar Library in Flutter to Communicate with Android/iOS Calendar](https://itnext.io/using-device-calendar-library-in-flutter-to-communicate-with-android-ios-calendar-95b2d8c77b40)