File Picker in Flutter

## はじめに
この記事ではFlutterでファイル取り出し機能を実現する方法を紹介します。
この記事で言うFile Picker（ファイル取り出し機能）とは、iOSで言う[Document Picker](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/DocumentPickerProgrammingGuide/Introduction/Introduction.html)、Androidで言うをACTION_GET_CONTENTのインテントを使ってiCloudやGoogle Driveなどからファイルを取り出す機能のことを指します。


## file_picker
[file_picker](https://pub.dev/packages/file_picker)というライブラリを使います。
言ってしまえばこれがすべてなので、もう自分でできるぜって方はもうこの記事を読まなくてもいいかもです。

READMEに使い方は載ってますが簡単に説明すると、メソッドによって単数選択と複数選択が可能で、FileTypeによって何を取り出すかを指定できます。

- FileType.ANY: なんでも
- FileType.CUSTOM: 拡張子を指定してマッチするファイルを取り出す
- FileType.IMAGE: 画像
- FileType.VIDEO: 動画
- FileType.AUDIO: オーディオ

```dart
// FileTypeによって何をpickするかを選択できる

// 単数選択
final String filePath = await FilePicker.getFilePath(type: FileType.ANY);
// 複数選択
final Map<String, String> filePaths = await FilePicker.getMultiFilePath(type: FileType.IMAGE);
```

とてもシンプルで簡単ですが、使用する前に準備がいくつか必要です。
file_pickerは上記のようにファイルの取り出しだけでなく画像や動画の取り出しも可能ですが、今回はファイルの取り出しにフォーカスして紹介します。
リポジトリのexampleを触ってみるとできることがよりイメージできるのでおすすめです。

## iOS側の設定
[file_pickerのREADME](https://github.com/miguelpruivo/plugins_flutter_file_picker)にもあるように、Androidの設定は簡単ですがiOSの設定が少々面倒です。
file_pickerでファイル取り出しを行う場合、iOSではDocument Pickerを使うため、Document Pickerを使用するための設定が必要です。
この設定自体はググればいくつかヒットしますが、一応この記事でも書いておきます。

まず、Xcode上のCapabilitiesからiCloudをONにします。
ServicesセクションではDocument Pickerを使う分にはiCloud Documentsだけを選択すればOKです。
次にContainersセクションでどのContainerを使うかを選択する必要があります。

![スクリーンショット 2019-06-24 19.33.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42132/67de6f0a-2e48-e27b-e76a-e4c16ebd0233.png)

このとき、Stepsに
> Provisioning profile {profile名} doesn't support the iCloud capability

と表示されます。
これは[Apple Developer](https://developer.apple.com/account/)上でiCloud Containerがまだ作られていない、かつ、Provisioning Profileにそれが設定されていない状態になるので、それらをApple Developer上で設定する必要があるということになります。

### iCloud Containerの作成
iCloud ContainerはApple DeveloperのIdentifiersタブの`+`ボタンから作成できます。

<img width="1698" alt="スクリーンショット 2019-06-24 19.43.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42132/576c530e-2fd4-f75c-ea7c-676b0321621b.png">

<img width="1698" alt="スクリーンショット 2019-06-24 19.43.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42132/6be454f3-5747-bb40-7073-060901673758.png">

<img width="1698" alt="スクリーンショット 2019-07-03 11.45.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42132/3c347e81-5c0c-63d8-3a68-5632e93b3317.png">


Xcodeでのデフォルトの設定では、iCloud Containerのidは`iCloud.$(CFBundleIdentifier)`となるはずなので、それと同じidを設定します。
例えばBundle IDが`jp.co.companyname.appname`の場合、iCloud Containerのidは`iCloud.jp.co.companyname.appname`となります。
DescriptionはなんでもOKです。
開発環境がstg,devなど複数ある場合はBundle IDも複数あると思うので、同じ数だけiCloud Containerを作成しましょう。

### Bundle IDとProvisioning Profileの更新
iCloud Containerを作成したらそれをBundle IDに作成したContainerを紐付けます。
Identifiersタブから該当するアプリのidを選択すると編集ができるので、そこでiCloudを選択します。
Configureボタンが現れるのでそこをクリックして作成したContainerと紐付けます。

<img width="1698" alt="スクリーンショット 2019-07-03 12.01.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42132/750acefb-d8a6-9d48-6471-d43771740ada.png">

すると現在使用しているProvisioning Profileはinvalidステータスになって使えなくなるので、新しくprofileを作ってそれを自分のPCに反映させれば準備は完了です。


## 弊アプリではこんな感じで使いました

弊アプリではPDFに絞ったファイル取り出しを行いたかったため、FileType.CUSTOMを使用して拡張子が.pdfのファイルのみを取り出すようにしました。

```dart
try {
  final file = await FilePicker.getFile(
    type: FileType.CUSTOM,
    fileExtension: 'pdf',
  );

  // ファイル選択画面でキャンセルした場合はnullになる
  if (file == null) {
    return;
  }

  final fileName = file.path.split('/').last;
  if (/* ファイル名バリデーション */) {
    // 警告表示
    return;
  }

  final fileSize = await file.length();
  if (/* ファイルサイズバリデーション */) {
    // 警告表示
    return;
  }

  // fileを送信
} catch (e) {
  // エラーハンドリング
}
```

このようにCUSTOMで拡張子を絞ると、両OSともファイル選択画面で指定した拡張子以外を選択できなくなるのでとても使い勝手がいいです。

## Bitriseでの注意点（おまけ）
弊アプリではCIに[Bitrise](https://www.bitrise.io/)を使用しています。
ファイル取り出し機能を実装後、Bitrise上でのiOSビルドのarchive時に少しつまずいたのでおまけとして書いておきます。

iCloudをcapabilitiesに加えると、iCloud Containerのenvironmentの指定が必要になります。
BitriseのXcode Archiveステップでは下の画像のように、`iCloud container environment`で指定できます。

![スクリーンショット 2019-06-28 15.04.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42132/93c60856-c2c6-7232-bea3-c48f7bac51e7.png)

ここに何も入れないとビルドがこけます。
archiveするときは大体配布するときだと思うので、基本的には`Production`を指定すればいいかと思います。