React Native 이미지 Firebase Storage에 업로드하기
React Native에서 이미지를 업로드하기 위해선 react-native-image-picker 라이브러리를 사용하면 된다.
이 라이브러리를 사용하면 서진첩에서 이미지를 선택하거나 카메라로 사진을 촬영할 수 있다.
유사한 라이브러리로는 @react-native-community/cameraroll이 있는데, 이 라이브러리는 이미지를 선택하는 UI를 react-native로직으로 직접 만들 수 있다.
1. 라이브러리 설치
먼저 react-native-image-picker 라이브러리를 설치해준다.
$ yarn add react-native-image-picker
$ npx pod-install
2. iOS 설정
ios/프로젝트명/Info.plist 파일 하단에 아래 코드를 추가한다. 이 작업은 카메라 및 갤러리 사용 권한을 위해 필요하다.
...
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) would like access to your photo gallery</string>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) would like to use your camera</string>
<key>NSPhotoLibraryAppUsageDescription</key>
<string>$(PRODUCT_NAME) would like to save photos to your photo gallery</string>
</dict>
</plist>
3. 안드로이드 설정
안드로이드 설정에서는 android/app/src/main//AndroidManifest.xml 파일에 두 가지 권한을 넣어주어야 한다.
반드시 필요한 작업은 아니지만 카메라로 찍은 사진을 바로 저장하거나 SD카드에 있는 사진을 읽어올 때 필요하다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.프로젝트명">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> // 추가
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> // 추가
...
위에 작업을 모두 마쳤으면 iOS, 안드로이드 시뮬레이터를 reload 해준다.
$ yarn ios
$ yarn android
4. 라이브러리 사용
react-native-image-picker 라이브러리는 launchCamera와 launchImageLibrary 두 API를 제공한다.
launchCamera(options, callback)
이 API는 사용할 이미지를 카메라로 바로 촬용할 때 사용한다.
options는 생략할 수 있으며, callback은 카메라로 이미지를 선택한 후 호출하는 함수다.
launchImageLibrary(options, callback)
이 API는 갤러리에서 이미지를 선택할 때 사용한다.
아래는 launchCamera와 launchImageLibrary API에 사용되는 options과 callback 항목이다.
options
- mediaType : photo 또는 video
- maxWidth : 이미지의 가로 폭을 리사이즈할 때 사용
- maxHeight : 이미지의 세로 폭을 리사이즈 할 때 사용
- videoQuality : 영상을 선택할 때 화질을 설정한다.
- iOS에서는 low, medium, high를, 안드로이드에서는 low, high를 선택할 수 있다.
- quality : 이미지 화질을 설정(0 ~ 1)
- includeBase64 : 이 값을 true로 지정하면 이미지를 base64형식으로 인코딩한다.
- saveToPhotos : launchCamera에서만 사용하는 설정으로 이 값을 true로 설정하면 카메라로 촬영한 후 이미지를 갤러리에 따로 저장한다. 이 옵션을 사용하려면 AndroidManifest.xml에서 WRITE_EXTERNAL_STORAGE 권한을 설정해야 한다.
- selectionLimit : 선택할 이미지의 수를 설정한다. 기본값은 1이며 0을 넣으면 무제한으로 선택할 수 있다.
callback
- didCancel : 사용자가 선택을 취소하면 true가 된다.
- errorCode : 에러에 대한 코드 정보를 지니고 있다. 에러의 종류는 react-native-image-picker GitHub에서 확인할 수 있다.
- errorMessage : 에러 메세지를 지니고 있다. 개발 과정에서 디버깅할 때만 사용한다.
- assets : 선택한 이미지의 정보 객체 배열이다. asset 객체는 다음 정보들을 지니고 있다.
- - base64 : base64로 인코딩 된 이미지 값
- - uri : 선택한 이미지의 경로
- - width : 선택한 이미지의 가로 폭
- - height : 선택한 이미지의 세로 폭
- - fileSize : 선택한 이미지의 크기
- - type : 선택한 이미지의 파일 타입
- - fileName : 선택한 파일의 이름
그럼 launchImageLibrary를 사용해서 갤러리에 있는 이미지를 선택해보자.
import React, { useState } from "react";
import { Pressable, Image } from "react-native";
import { launchImageLibrary } from "react-native-image-picker";
...
function ImageUploadSample() {
const [response, setResponse] = useState(null);
const onSelectImage = () => {
launchImageLibrary(
{
mediaType: "photo",
maxWidth: 512,
maxHeight: 512,
includeBase64: Platform.OS === 'android',
},
(res) => {
console.log(res);
if (res.didCancel) return;
setResponse(res);
},
)
}
return (
...
<Pressable style={styles.circle} onPress={onSelectImage}>
<Image style={styles.circle} source={{uri: response?.assets[0]?.uri}} />
</Pressable>
...
)
}
export default ImageUploadSample;
안드로이드의 경우에는 이미지를 base64로 인코딩해두고, 업로드할 때 base64로 인코딩된 결괏값을 사용해 업로드를 진행해야 한다.(이 권한 이슈는 Google Photo를 사용하는 기기에서 발생)
Pressable을 클릭해 이미지를 선택해보자.
5. Firebase Storage에 업로드 하기
Firebase Storage 라이브러리를 설치하는 방법은 [React Native] - React Native 프로젝트에 Firebase 연동하기에 정리되어 있다.
Firebase의 Storage는 무료로 사용할 수 있으며, 총 파일 용량 최대 5GB, 일일 다운로드 최대 1GB의 제한이 있다.
import React, { useState } from "react";
import { Platform, Pressable, Image, Button, ActivityIndicator } from "react-native";
import { launchImageLibrary } from "react-native-image-picker";
...
function ImageUploadSample() {
const [response, setResponse] = useState(null);
const onSelectImage = () => {
launchImageLibrary(
{
mediaType: "photo",
maxWidth: 512,
maxHeight: 512,
includeBase64: Platform.OS === 'android',
},
(res) => {
console.log(res);
if (res.didCancel) return;
setResponse(res);
},
)
}
const [loading, setLoading] = useState(false);
const imageUpload = async () => {
setLoading(true);
let imageUrl = null;
if (response) {
const asset = response.assets[0];
const reference = storage().ref(`/profile/${asset.fileName}`); // 업로드할 경로 지정
if (Platform.OS === "android") { // 안드로이드
// 파일 업로드
await reference.putString(asset.base64, "base64", {
contentType: asset.type
});
} else { // iOS
// 파일 업로드
await reference.putFile(asset.uri);
}
imageUrl = response ? await reference.getDownloadURL() : null;
}
console.log("imageUrl", imageUrl);
// imageUrl 사용 로직 ...
}
return (
...
<Pressable style={styles.circle} onPress={onSelectImage}>
<Image style={styles.circle} source={{uri: response?.assets[0]?.uri}} />
</Pressable>
{loading ? (
<ActivityIndicator size={32} color="#6200ee" style={styles.spinner} />
) : (
<Button style={styles.button} title="다음" onPress={imageUpload}/>
)}
...
)
}
export default ImageUploadSample;
위 코드에서 안드로이드일때는 putString을 통해 base64로 인코딩된 테이블로 업로드 하고, iOS일때는 uri에서 파일을 불러와 바로 업로드한다. 이미지 업로드는 보통 1~3초 정도 걸리기 때문에 업로드 하는 동안 ActivityIndicator 컴포넌트로 스피너를 보여주는 것도 좋다.
예제를 실행하고 Firebase Storage에 들어가보면 업로드 한 파일을 확인할 수 있다.
Error: [storage/unauthorized] User is not authorized to perform the desired action
만약 위 코드를 실행하며 위 오류가 발생할때는 Firebase > Storage > Rules에 들어가서 if false로 되어있는 부분을 if true로 변경해주면 정상적으로 작동한다.