사진 업로드는 expo-image-picker로 해결되는데, 사용자가 올려야 할 게 사진이 아니라 PDF 계약서나 엑셀 파일이면 얘기가 다르다. 이력서 첨부, 영수증 PDF, CSV 가져오기 같은 기능을 붙이려면 갤러리가 아니라 파일 앱을 열어야 한다. 이럴 때 쓰는 게 expo-document-picker다.
설치
npx expo install expo-document-picker
네이티브 파일 선택 UI(iOS는 Files, Android는 문서 선택기)를 그대로 띄워주기 때문에 별도 권한 설정도 필요 없다.
파일 하나 고르기
getDocumentAsync를 호출하면 시스템 파일 선택기가 뜨고, 사용자가 고른 파일 정보가 돌아온다.
import * as DocumentPicker from 'expo-document-picker';
async function pickPdf() {
const result = await DocumentPicker.getDocumentAsync({
type: 'application/pdf',
copyToCacheDirectory: true,
});
if (result.canceled) return;
const file = result.assets[0];
console.log(file.name, file.size, file.uri);
}
여기서 처음 헷갈렸던 게 결과 구조다. 예전 버전은 result.type === 'success'로 분기했는데, 지금은 result.canceled 불리언을 먼저 보고, 실제 파일은 result.assets 배열에 담겨 온다. 취소 처리를 빼먹으면 assets[0]에서 바로 터진다.
type에 MIME 타입을 넣으면 선택 가능한 파일을 제한할 수 있다. 여러 종류를 허용하려면 배열로 넘긴다.
type: ['application/pdf', 'text/csv', 'image/*'],
copyToCacheDirectory를 켜야 하는 이유
copyToCacheDirectory: true가 핵심이다. 이걸 빼면 받은 uri가 content://나 보안 스코프 URI라서, 선택기가 닫히는 순간 접근 권한이 사라진다. 나중에 그 파일을 읽거나 서버에 올리려 하면 "파일 없음" 에러가 난다. 캐시로 복사해두면 앱 샌드박스 안의 안정적인 file:// 경로가 생겨서 업로드든 파싱이든 마음 편하게 할 수 있다.
업로드와 용량 체크
고른 파일은 FormData에 그대로 실어 보내면 된다. 올리기 전에 size로 용량을 거르는 습관을 들여두는 게 좋다. 사용자는 생각보다 거대한 파일을 아무렇지 않게 고른다.
if (file.size && file.size > 10 * 1024 * 1024) {
alert('10MB 이하 파일만 올릴 수 있어요');
return;
}
const form = new FormData();
form.append('file', {
uri: file.uri,
name: file.name,
type: file.mimeType ?? 'application/octet-stream',
} as any);
정리하면 포인트는 세 개다. canceled를 먼저 확인하고 파일은 assets에서 꺼낼 것, copyToCacheDirectory를 반드시 켤 것, 그리고 업로드 전에 size로 용량을 거를 것. 이 세 개만 챙기면 사진이 아닌 파일 업로드도 이미지 업로드만큼 간단하게 끝난다.