前にも記事を書いたのですが、いろいろなところからの寄せ集めだったので改めてまとめてみました。
主な流れ
Expo + Firebaseを利用したアップロード処理は以下のようになります。
- CAMERAパーミッションを取得
- ImagePckerを起動し写真を取る(ファイルを選ぶ)
- アップできるよう取得したファイルをblobに変換
- アップロード先を指定してアップロード
- プログレス等を処理
- downloadURLを取得して表示に利用
サンプルの仕様
ボタンとか配置するのがめんどくさいのでreac-native-elementsのAvatarを利用して画像の選択、表示をやってみます。
まあProfile画像とかでも使いますしね。Imageとかでも同じです(コメントアウトして入れてます)。
いちおうプログレスとかも拾ってみます。
実装
必要モジュールのインストール
シンプルなわりにいろいろ使ってます。
npm install --save expo-constants expo-permissions expo-image-picker
npm install --save firebase react-native-elements
Firebase
Firebaseへの登録やプロジェクト作成は当然として、App.jsと同じ階層にFirebase.jsを設置し、以下のようにしています。
Config情報は各自の環境に合わせます。
Firebase.js
import firebase from 'firebase';
const firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "xxxxxxxx.firebaseapp.com",
databaseURL: "https://xxxxxxxx.firebaseio.com",
projectId: "xxxxxxxxx",
storageBucket: "xxxxxxxxx.appspot.com",
messagingSenderId: "xxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
measurementId: "xxxxxxxxx"
};
firebase.initializeApp(firebaseConfig);
export default firebase;
実装
流れがわかるように1つの関数に実装しています。
以前実装した際はPermissionはCAMERA_ROLLを取得する必要がありましたが、今回はCAMERAじゃないとエラーになりました。
App.js
import React from 'react';
import { StyleSheet, Text, View, Image } from 'react-native';
import { Avatar, Button } from 'react-native-elements';
import firebase from './Firebase';
import Constants from 'expo-constants';
import * as ImagePicker from 'expo-image-picker';
import * as Permissions from 'expo-permissions';
class App extends React.Component {
state = {
url: 'https://firebasestorage.googleapis.com/v0/b/test-fc01e.appspot.com/o/noimage.png?alt=media&token=0606e1b4-e817-4859-83d4-7920c99e14c0',
progress: '',
}
ImageChoiceAndUpload = async () => {
try {
//まず、CAMERA_ROLLのパーミッション確認
if (Constants.platform.ios) {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
if (status !== 'granted') {
alert("利用には許可が必要です。");
return;
}
}
//次に、画像を選ぶ
const result = await ImagePicker.launchCameraAsync();
if (!result.cancelled) {
//撮影された(ローカルの)写真を取得
const localUri = await fetch(result.uri);
//blobを取得
const localBlob = await localUri.blob();
//filename 実際はUIDとかユーザー固有のIDをファイル名にする感じかと
const filename = "profileImage"
//firebase storeのrefを取得
const storageRef = firebase.storage().ref().child("images/" + filename);
//upload
// const putTask = await storageRef.put(localBlob);
//進捗を取得したいのでawaitは使わず
const putTask = storageRef.put(localBlob);
putTask.on('state_changed', (snapshot) => {
let progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
this.setState({
progress: parseInt(progress) + "%",
});
}, (error) => {
console.log(error);
alert("アップロードに失敗しました。サイズが大きいとか。");
}, () => {
putTask.snapshot.ref.getDownloadURL().then(downloadURL => {
console.log(downloadURL);
this.setState({
progress: '',
url: downloadURL,
});
})
})
}
} catch (e) {
console.log(e.message);
alert("サイズが多き過ぎるかも。");
}
}
render() {
return (
<View style={{ marginTop: 100, alignSelf: 'center' }}>
<Text>画像アップロード</Text>
<View style={{ margin: 20 }}>
<Avatar
size="large"
rounded
title="NI"
onPress={this.ImageChoiceAndUpload}
source={{ uri: this.state.url }}
/>
<Text style={{ alignSelf: 'center' }}>{this.state.progress}</Text>
</View>
{/* <Image
source={{ uri: this.state.url }}
style={{ width: 100, height: 100, alignSelf: 'center' }}
/> */}
</View>
);
}
}
export default App;
その他
アップロードの容量制限等をしたいところですが、Storageのルールを使うこともできるようです。あとはextensionのresizeとかもいいかもしれません。
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read;
allow write: if request.resource.size < 3 * 1024 * 1024; //3Mまで
}
}
}