Flutter X FirebaseでMulti_image_pickerを用いて複数の画像のURLをFireStoreに保存する方法

ポートフォリオ作成のためにMulti_image_pickerを用いて複数の画像を保存しようと思った時に
一括でできる方法がドキュメントに書いていなかったため、メモがてら...という感じで大雑把に書こうと思います。

また今回はBlocを使って書いてますがそこの説明も端折ります。。

ちなみに各種versionは以下の通りです。

    firebase_storage: ^3.1.6
    cloud_firestore: ^0.13.7
    multi_image_picker: ^4.7.12

また細かい初期設定についてはドキュメントに書いてある通りです。

##1. MultiImagePickeを用いて複数の画像を選択。

大半はドキュメント通りです。
画像を追加のボタンを押すとPickPlaceImagesのeventが走る感じです。  

```
if(state is PickedPlaceImages){
          return Container(
            child: Column(
              children: <Widget>[
                SizedBox(height: 20),
                Container(
                  child: buildListView(state.images),
                  height: 150,
                ),
                SizedBox(height: 20),
                RaisedButton(
                  child: Text(
                    "画像を追加",
                    style: TextStyle(
                      color: Colors.lightBlueAccent,
                    ), 
                  ),
                  onPressed: () => BlocProvider.of<ImageBloc>(context).add(PickPlaceImages()),
                ),
                SizedBox(height: 20),
              ],
            ),
          );
        } 

```

buildListViewは以下のような感じです。公式ではGridだったところをListViewに変えてます。

```
Widget buildListView(List<Asset> images) {
    return Center(
      child: new ListView.builder(
        shrinkWrap: true,
        scrollDirection: Axis.horizontal,
        itemCount: images.length,
        itemBuilder: (context, index){
          return Container(
            decoration: BoxDecoration(
              border: Border.all(),
            ),
            child: AssetThumb(
              asset: images[index],
              width: 150,
              height: 150,
            ),
          );
        }
      ),
    );
  }
```
 
またPickPlaceImagesによって以下のような処理が走ります。  

```
Stream<ImageState> _mapPickPlaceImagesToState() async* {
    List<Asset> resultList;
    String error;
    try{
      resultList = await MultiImagePicker.pickImages(
        maxImages: 20,
        enableCamera: true,
      );
    } on Exception catch(e) {
      error = e.toString();
      yield PickPlaceImagesFail(error);
    }
    if(resultList != null)yield PickedPlaceImages(resultList);
    else yield PickImageInitial();
  }
```

これもドキュメントに書いてある通りの処理が大半で、画像が1枚以上選択されていた場合にはstateの引数としてAssetのリストを返してあげている感じです。

ここまでで、画像を選択し、AssetのListを作成するところまでの実装ができます。

##2. FireStorageを用いて画像のURLを取得  
続けて上記のリストを元に画像をFireStorageに保存 & URLを取得まで一度に行います。
ちなみにここの部分も大半はドキュメントに書いてあります。

        return RaisedButton(
           child: Text("保存"),
           onPressed: (){
             BlocProvider.of<PlaceBloc>(context).add(
               GetCreatePlace(
                 place: Place(
                   id: 1,
                   name: _name,
                   createdAt: DateTime.now(),
                   updatedAt: DateTime.now(),
                 ), 
                 images: state.images,
               ),
             );
             Navigator.pop(context);
           },
         );


上記の保存ボタンを押すと、GetCratePlaceのEventが走ります。
また引数に渡している、state.imagesは1.で選択したimage(Asset型)のリストです。
またGetCreatePlaceのEventでは以下のことを行なっています。

```
Stream<PlaceState> _mapCraetePlaceToState(Place place, List<Asset> images)async* {
    try{
      await _getImageUrls(place,uid,images);
      yield PlaceCreated(name: place.name);
    } catch(e) {
      print(e);
    }
  }
```

主な処理は_getImageUrlsの中で行っています。

```
Future<void> _getImageUrls(Place place, String uid, List<Asset> images) async {
  await _imageRepository.saveImages(images, place, place.creatorId).then((urls){
    _placeRepository.createPlace(place, uid, urls);
  });
}
```

簡単に説明をすると、
saveImagesがimagesをstorageに保存、urlのリストを返してくるという実装で,
返ってきたurlのリストをcreatePlaceに渡してあげることでfirestoreにurlを保存するという流れです。


続けてsaveImagesの中身を見ていきます。

```
Future<dynamic> _saveImage(int index, Asset asset, String name, String uid) async {
    ByteData byteData = await asset.getByteData();
    List<int> imageData = byteData.buffer.asUint8List();
    StorageReference ref = FirebaseStorage.instance.ref().child('$uid/$name/$index.jpg');
    StorageUploadTask uploadTask = ref.putData(imageData);
    return await (await uploadTask.onComplete).ref.getDownloadURL();
  }

  Future<List<dynamic>> saveImages(List<Asset> assets, Place place, String uid) async {
    return await Future.wait(assets.indexedMap((index, asset) async {
      return await _saveImage(index, asset, place.name, uid);
    }).toList());
  }
```

imageRepositoryには画像の処理に関する記述が書いてあり、saveImagesではまず初めに、引数のassetsをindexedMap(Extensionで、indexも得られるmap)によって順番に_saveImageを呼び出してあげています。
(ここでFuture.waitを付けないと、nullの配列が先にreturnしてしまうので注意です。)

また_saveImageの中身はDocumentにあるとおり、一つのAssetを元に保存場所を指定してStorageに保存する処理となっています。(今回はuidとnameごとにフォルダを分けて保存しています。)

これで複数のimagesをstorageへ保存することができます。

##3. 複数のURLをFirestoreへ保存。
_getImageUrlでurlsに欲しいデータが返ってくるので、これを元にFirestoreへデータの保存を行います。

```
class PlaceRepository extends PlaceDataRepository{
  final CollectionReference placesCollection = Firestore.instance.collection("places");
  @override
  Future<void> createPlace(Place place, String uid, List<dynamic> urls) async {
    try{
      return await placesCollection.document(uid).setData({
        'name': place.name,
        'creatorId': uid,
        'images': urls,
      });
    }catch(e){
      print(e);
    }
  }
}
```

こちらに関してはなんの捻りもないです。


以上で、複数の画像を選択=> storageに保存 => firestoreにURLを保存
ができるかと思います。


### 参考
[multi_image_picker](#https://sh1d0w.github.io/multi_image_picker/#/upload)