Flutter×Firebase(CloudStorage)でファイルダウンロードとアップロードを簡単に実現する方法

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認ください</b></font>
* 25万文字を超える超大作になっています!!

---

## まとめ記事
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)

# はじめに
* よく使うファイルサーバとの連携を確認しました。


# 概要

Firebase Storage(以下Storage)はファイルサーバとして、ファイルのダウンロード、アップロードを管理する環境を提供します。


# 有効化手順
Storageの有効化は、Firebaseの左側のメニューの**Storage**を選択し、始めるをクリックします。

![pic1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/a4591ad4-3b77-d7ca-1406-632097fcc4c7.png)
![pic2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/0c5b19bd-5313-b602-a10c-1bcce874f7a8.png)
![pic3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/6976ef75-3612-b6c4-bfa4-cbcc290d8c97.png)


![pic4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/f6490088-be99-bb9d-976c-0e050d24c9af.png)

今後の動作確認用に、ダウンロード用とアップロード用のフォルダを作成します。

![pic5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/f295b48a-1638-6145-08f1-21c8c5f6610a.png)


さらに、ダウンロード用のフォルダの下に、hello.txt(helloとかかれたファイル)とicon.png(flutterのロゴのpng)の2つのファイルを準備します。

![pic6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/d4bda012-f1ba-ecf5-adbe-50a41561c9e0.png)


# 準備

```yaml:pubspec.yaml
  firebase_core: ^1.1.0
  firebase_auth: ^1.1.1
  firebase_storage: ^8.0.5
  path_provider: ^2.0.1
  image_picker: ^0.7.4
```
`firebase_core`と`firebase_storage`はstorageを使うために必要なパッケージです。
`firebase_auth`はFirebaseで認証を行うために必要なものです。
※storageは完全なフリーアクセスではなく、認証したユーザで行うために用います。
`path_provider`や`image_picker`はデバイスへの保存や画像の選択などを行うための補助的なパッケージです。

# 実装

ダウンロードでは、文字と画像のファイルをダウンロードし、文字の表示と画像の画面への表示、そして、画像ファイルのローカルへの保存を行います。

アップロードでは、`image_picker`を使ってファイルを選択し、アップロードします。

```dart:main.dart
import 'dart:io';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'dart:convert';

import 'package:firebase_storage/firebase_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Image _img;
  Text _text;

  Future<void> _download() async {
    // ログイン処理
    await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: "test@test.com", password: "testtest");

    // ファイルのダウンロード
    // テキスト
    FirebaseStorage storage = FirebaseStorage.instance;
    Reference textRef = storage.ref().child("DL").child("hello.txt");
    //Reference ref = storage.ref("DL/hello.txt"); // refで一度に書いてもOK

    var data = await textRef.getData();

    // 画像
    Reference imageRef = storage.ref().child("DL").child("icon.png");
    String imageUrl = await imageRef.getDownloadURL();

    // 画面に反映
    setState(() {
      _img = Image.network(imageUrl);
      _text = Text(ascii.decode(data));
    });

    Directory appDocDir = await getApplicationDocumentsDirectory();
    File downloadToFile = File("${appDocDir.path}/download-logo.png");
    try {
      await imageRef.writeToFile(downloadToFile);
    } catch (e) {
      print(e);
    }
  }

  void _upload() async {
    // imagePickerで画像を選択する
    // upload
    PickedFile pickerFile =
        await ImagePicker().getImage(source: ImageSource.gallery);
    File file = File(pickerFile.path);

    FirebaseStorage storage = FirebaseStorage.instance;
    try {
      await storage.ref("UL/upload-pic.png").putFile(file);
    } catch (e) {
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              if (_text != null) _text,
              if (_img != null) _img,
            ],
          ),
        ),
        floatingActionButton:
            Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
          FloatingActionButton(
            onPressed: _download,
            child: Icon(Icons.download_outlined),
          ),
          FloatingActionButton(
            onPressed: _upload,
            child: Icon(Icons.upload_outlined),
          ),
        ]));
  }
}
```
まず、firebaseを利用するために以下の2行で初期化処理をしています。
`WidgetsFlutterBinding.ensureInitialized();`
`await Firebase.initializeApp();`

ダウンロード用とアップロード用のボタンがあり、それぞれがダウンロードとアップロードに対応しています。

## ダウンロード
ダウンロードを行っているのは、`_download`です。

まず、FirebaseAuthenticationを使った認証を行っています。
予めID「test@test.com」とPW「testtest」というユーザをFirebase側に作っておきます。

FirebaseAuthenticationの概要・設定方法については、下記をご参照ください。
* [Firebaseの機能1-1:Authentication(メール)](https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/firebase-authentication)
* [Firebaseの機能1-2:Authentication(Google認証)](https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/firebase-authentication2)


その後、`FirebaseStorage.instance`でインスタンスを作成し、`storage.ref`にパスを与えて参照を取得します。
refにパスをすべて書いても良いですし、階層ごとにchildでメソッドチェインにしても良いです。

データはrefの`getData`メソッドで取得できますが、unit8List型のため、変換する場合には一工夫が必要です。
また、本体をダウンロードする場合は、`getDownloadURL`でURLを取得し、このURLからダウンロードします。

テキストは`getData`を利用し、画像はダウンロードしてデータを取得しています。

最後に、ファイルをデバイスにダウンロードするために、`getApplicationDocumentsDirectory`でパスを取得して、`writeToFile`で出力しています

## アップロード
アップロードを行っているのは、`_upload`です。
`ImagePicker`を使って画像を選択し、そのファイルをStorageにアップロードしています。

インスタンスの生成や参照の作成は同じです。
`putFile`でファイルをサーバ側に配置します。

# 動作イメージ
## ダウンロード

![pic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/d2132ef0-fbb8-66b8-34b5-a05a59de9714.png)

最初は画面には何もありませんが、ダウンロードを行うと、画面に文字や画像が表示されます。


# アップロード

![pic8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/66b4300d-5b9a-1c76-c2de-6505274522d0.png)

続けて、アップロードを行うと、`ImagePicker`が起動しファイルを選択できるようになります。

![pic9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/2421f9eb-88e6-ef78-4a6e-553e0a219db5.png)

アップロードされたファイルはサーバ側からも確認できます。