Flutter の DatePicker に関するTips

## はじめに

[ユアマイスター Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yourmystar) の12日目の記事です。

ユアマイスターでは、 [あなたのマイスター](https://yourmystar.jp/) でハウスクリーニングや修理などのサービスを提供するプロや職人が利用する店舗向けアプリ「マイスターアプリ」 ([Android版](https://apps.apple.com/jp/app/id1478724946?l=ja), [iOS版](https://apps.apple.com/jp/app/id1478724946?l=ja)) があります。
このアプリでは、Flutterを採用しており、2019年10月にリリースされました。(その他の技術スタックは [こちら](https://yourmystar-engineer.hatenablog.jp/entry/2019/11/25/090000))

今回は、DatePickerを使ったTipsを紹介したいと思います。

## 基本

まず、DatePickerを使いたいときは `showDatePicker` メソッドを使います。
これを使うと、日付選択できるダイアログが画面に立ち上がります。

```dart
class PickerDemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: IconButton(
          icon: Icon(Icons.calendar_today),
          onPressed: () async {
            final selectedDate = await showDatePicker(
              context: context,
              initialDate: DateTime.now(),
              firstDate: DateTime(DateTime.now().year),
              lastDate: DateTime(DateTime.now().year + 1),
            );
            
            if (selectedDate != null) {
              // do something
            }
          },
        ),
      ),
    );
  }
}
```

これは、required となっている引数だけを使ったサンプルです。
簡単に説明すると、

- `initialDate` : はじめに選択されている日付
- `firstDate`: 選択可能な最小の日付
- `lastDate`: 選択可能な最大の日付

返ってきた値をNullチェックしているのは、キャンセルボタンやダイアログの外をタップしてダイアログを閉じたとき、Nullが返ってくるためです。

## 先に年を選択させる

```dart
showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(DateTime.now().year),
  lastDate: DateTime(DateTime.now().year + 6),
  initialDatePickerMode: DatePickerMode.year,
);
```

`initialDatePickerMode` を `DatePickerMode.year` にすると、年選択 -> 月日選択ができるようになります。
生年月日を選択するときなどに使えます。

## 指定した日を選択できないようにする

```dart
showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(DateTime.now().year),
  lastDate: DateTime(DateTime.now().year + 6),
  selectableDayPredicate: (DateTime date) =>
    date.weekday == 6 || date.weekday == 7 ? false : true,
);
```

`selectableDayPredicate` を false にすることで、特定の日を選択できないようにできます。
ただし、`initialDate` で初期選択に選んだ日は選択された状態で表示されるので、少し注意が必要です。
上記のサンプルは、平日のみ選択可能にしたものです。

## 日本語化する

まず、Flutterを多言語対応する必要があります。
これからざっと書いていきますが、
詳しく知りたい方は、以下の公式ドキュメントを見てください。

https://flutter.dev/docs/development/accessibility-and-localization/internationalization

### flutter_localizations パッケージをインストール

```yaml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
```

pubspec.yaml に追加したあとは、 `flutter pub get` のコマンドでインストール。

### アプリが対応する言語のロケールを指定する

```dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

MaterialApp(
 localizationsDelegates: [
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
   GlobalCupertinoLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en'),
    const Locale('ja'),
  ],
  // ...
)
```

インストールした `flutter_localizations` をimportして、 MaterialAppに `localizationsDelegates` と `supportedLocales` を設定。

### showDatePicker() でロケールを設定する

```dart
showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(DateTime.now().year),
  lastDate: DateTime(DateTime.now().year + 6),
  locale: const Locale('ja'),
);
```

これで、 ”xxxx年xx月xx日” というフォーマットで表示されます。

## DatePickerの色を変える

```dart
showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(DateTime.now().year),
  lastDate: DateTime(DateTime.now().year + 6),
  builder: (BuildContext context, Widget child) {
    return Theme(
      data: ThemeData(
        // ヘッダーの色
        primaryColor: Colors.red,
        // 日付選択部の色
        dialogBackgroundColor: Colors.green,
        // 選択されたときのテキストの色
        accentTextTheme: TextTheme(
          body2: TextStyle(
            color: Colors.black,
          ),
        ),
        // 選択されたときの円の色
        accentColor: Colors.purple,
        // OK/CANCELボタンのテキストの色
        buttonTheme: const ButtonThemeData(
          textTheme: ButtonTextTheme.accent,
        ),
      ),
      child: child,
    );
  },
);
```

基本的には、MaterialApp で設定されたThemeでDatePickerの色が決まりますが、 上記の方法でDatePickerの色をカスタマイズできます。

## ダイアログでなく、画面にDatePickerを埋め込みたい
現状、DatePickerを画面内に表示するものは提供されていませんでした。
少し探してみたところ、[flutter_date_pickers](https://pub.dev/packages/flutter_date_pickers) があったので、それを使ってみてもいいかもしれません。

## まとめ

最後に、これまで紹介したコードのまとめたものを残しておきます。

```dart
final selectedDate = await showDatePicker(
    context: context,
    // 初期選択されている日付
    initialDate: DateTime.now(),
    // 選択可能な最小の日付
    firstDate: DateTime(DateTime.now().year),
    // 選択可能な最大の日付
    lastDate: DateTime(DateTime.now().year + 6),
    // 日付の選択形式
    initialDatePickerMode: DatePickerMode.year,
    // 指定した日を選択できないようにする
    selectableDayPredicate: (DateTime date) =>
        date.weekday == 6 || date.weekday == 7 ? false : true,
    // 日本語化
    locale: const Locale('ja'),
    // DatePickerの色を変える
    builder: (BuildContext context, Widget child) {
    return Theme(
        data: ThemeData(
        // ヘッダーの色
        primaryColor: Colors.red,
        // 日付選択部の色
        dialogBackgroundColor: Colors.green,
        // 選択されたときのテキストの色
        accentTextTheme: TextTheme(
            body2: TextStyle(
            color: Colors.black,
            ),
        ),
        // 選択されたときの円の色
        accentColor: Colors.purple,
        // OK/CANCELボタンのテキストの色
        buttonTheme: const ButtonThemeData(
            textTheme: ButtonTextTheme.accent,
        ),
        ),
        child: child,
    );
    },
);

// キャンセルボタンやダイアログの外をタップしてダイアログを閉じたとき、NULLが返ってくる
if (selectedDate != null) {
    // do something
}
```