계속: ReactNative (Expo)로 카메라로 찍은 사진을 Firebase Storage에 저장

전에도 기사를 쓴 입니다만, 여러가지 곳으로부터의 모임이었으므로 재차 정리해 보았습니다.

주요 흐름



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;
    

    구현



    흐름을 알 수 있도록 하나의 함수로 구현하고 있습니다.

    이전에 구현했을 때 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まで
        }
      }
    }
    

    좋은 웹페이지 즐겨찾기