Chapter 12

Storage:ファイルアップ:基礎

masalib
masalib
2020.12.26に更新

Firebaseにはイメージなどファイルを保存してくれるサービスの
Storageというものがあります。こちらのサービスを利用してイメージをアップする部分を作りたいと思います

事前作業

Firebase Storageを使うためには管理画面で初期設定をしないといけません

  1. Firebaseのプロジェクト画面からCloud Storageを作成します。
  2. 権限に関する注意が出てきます。そのまま「次へ」ボタンでOK。
  3. Cloud Storageのロケーションを選択します。ロケーションは後から変更できないので注意が必要ですが、
    とりあえず近い場所(asia northeast)を選択しておきます。

    4.プロジェクトにあわせた権限に変更をする
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /images/test/{imageId} {
        allow read, write: if request.auth != null;
    }
  }
}

Storageを作ってからも権限(ルール)は変更できます。(初期の段階では表示されているルールを変更できないかもしれません。)
今の所はtestup用なのでゆるいです。もしログイン部分を省略する場合は
すべて許可する設定にしてください

//allow read, write: if request.auth != null;
allow read, write: if true;

ちゃんとセキュリティーを設定したい人は以下のドキュメントを参照してください

https://firebase.google.com/docs/rules/basics?authuser=0

FireStoreの部分がメインですがこちらのページもわかりやすいです。

https://zenn.dev/kosukesaigusa/articles/efc2528898954d95a6ae

Firebaseの設定ファイル修正

/src/firebase.js
import firebase from "firebase/app";
import "firebase/auth";
+ import "firebase/storage";
const firebaseConfig = {
    apiKey: process.env.REACT_APP_APIKEY,
    authDomain: process.env.REACT_APP_AUTHDOMAIN,
    databaseURL: process.env.REACT_APP_DATABASEURL,
    projectId: process.env.REACT_APP_PROJECT_ID,
    storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_APP_ID,
    measurementId: process.env.REACT_APP_MEASUREMENT_ID
};

firebase.initializeApp(firebaseConfig);

var auth_obj = firebase.auth();
+ var storage_obj = firebase.storage();

export default firebase;
export const auth = auth_obj;
+ export const storage = storage_obj;

基本的にはStorageのオブジェクトを足しているだけで特に難しいところはないと思います。

補足

  • 終了時点でのソースはcodesandboxを使っているため、環境変数を使っていないですが環境変数を使うのが正しいです。

ファイルアップロードプログラム

/src/components/UpLoadTest.js
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { storage } from "../firebase";
import LinearProgress from "@material-ui/core/LinearProgress";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box";

const UpLoadTest = () => {
  const [image, setImage] = useState("");
  const [imageUrl, setImageUrl] = useState("");
  const [error, setError] = useState("");
  const [progress, setProgress] = useState(100);

  const handleImage = (event) => {
    const image = event.target.files[0];
    setImage(image);
    console.log(image);
    setError("");
  };

  const onSubmit = (event) => {
    event.preventDefault();
    setError("");
    if (image === "") {
      console.log("ファイルが選択されていません");
      setError("ファイルが選択されていません");
      return;
    }
    // アップロード処理
    console.log("アップロード処理");
    const storageRef = storage.ref("images/test/"); //どのフォルダの配下に入れるかを設定
    const imagesRef = storageRef.child(image.name); //ファイル名

    console.log("ファイルをアップする行為");
    const upLoadTask = imagesRef.put(image);
    console.log("タスク実行前");

    upLoadTask.on(
      "state_changed",
      (snapshot) => {
        console.log("snapshot", snapshot);
        const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        console.log(percent + "% done");
        setProgress(percent);
      },
      (error) => {
        console.log("err", error);
        setError("ファイルアップに失敗しました。" + error);
        setProgress(100); //実行中のバーを消す
      },
      () => {
        upLoadTask.snapshot.ref.getDownloadURL().then((downloadURL) => {
          console.log("File available at", downloadURL);
          setImageUrl(downloadURL);
        });
      }
    );
  };

  return (
    <div>
      upload
      {error && <div variant="danger">{error}</div>}
      <h2>
        <Link to="/Dashboard">Dashboard</Link>
      </h2>
      <form onSubmit={onSubmit}>
        <input type="file" onChange={handleImage} />
        <button onClick={onSubmit}>Upload</button>
      </form>
      {progress !== 100 && <LinearProgressWithLabel value={progress} />}
      {imageUrl && (
        <div>
          <img width="400px" src={imageUrl} alt="uploaded" />
        </div>
      )}
    </div>
  );
};

function LinearProgressWithLabel(props) {
  return (
    <Box display="flex" alignItems="center">
      <Box width="100%" mr={1}>
        <LinearProgress variant="determinate" {...props} />
      </Box>
      <Box minWidth={35}>
        <Typography variant="body2" color="textSecondary">{`${Math.round(
          props.value
        )}%`}</Typography>
      </Box>
    </Box>
  );
}
export default UpLoadTest;

ソース補足

  • このプログラムはファイル選択の段階ではアップロードが行かないです。

  • アップロードのボタンを押して初めてFirebaseのStorageに保存されます。

  • ファイル名はアップロードされた時のファイル名で保存されます。同じ名前では上書きされます。

  • ファイルアップロードはタスクを作ってタスクが実行されてアップロードします。

const storageRef = storage.ref("images/test/"); //どのフォルダの配下に入れるかを設定
const imagesRef = storageRef.child(image.name); //ファイル名
const upLoadTask = imagesRef.put(image);

upLoadTask.on(
・・・
  • ファイルのアップロード(タスク)はsnapshotとerrorという変数で管理されています。errorは失敗した時に入ります。snapshotは実行中状態、実行完了状態がはいります。
    実行中状態を工夫してアップロードの状況をユーザーに伝える事ができます。このソースだと
    material-uiのProgressを使ってユーザーにアップロード状態を伝えています。
    成功するとsnapshot.ref.getDownloadURL()でアップロードしたURLが取得できます。
    今回は作っていませんがタスクを止めたり、キャンセルしたりする機能も作れます。

  • errorの内容で切り替えをおこなっていません。もし切り替えをおこないたい場合は以下のようなソースを組んでください。

- console.log("err", error);
- setError("ファイルアップに失敗しました。" + error);
+ switch (error.code) {
+     case 'storage/unauthorized':
+       setError("アップロードの権限がないため、ファイルアップに失敗しました");
+       break;
+     case 'storage/canceled':
+       setError("ユーザーがオペレーションをキャンセルしました");
+       break;
+ 
+       ...
+ 
+     case 'storage/unknown':
+       setError("。不明なエラーが発生したためファイルアップに失敗しました" );
+       break;
+   }

エラーリストについては公式ドキュメントを参照してください

https://firebase.google.com/docs/storage/web/handle-errors?hl=ja

結果

Firebase側にファイルが上がっていることが確認できます

終了時点のソース

  • Guestログインした人は以下のアカウントを使ってください
    ID:test@test.com
    pass:test1234

  • この時点では画像の縦横のアスペクト比などは気にしていません。

  • また画像の圧縮などはやっていません。

参考URL

https://coders-shelf.com/react-firebase-image-upload/

https://qiita.com/shouhi/items/8cf7cac85fdcf204b22c