프로그래밍 언어/React Native (RN)

react-native-fs 사용법 (RN)

chobyeonggyu03 2024. 7. 24. 14:16
반응형

이번 글에서는 아직까지도 이미지를 파일형태로 서버 api 보낼 때 생기는 오류를 해결하지 못하여 'react-native-blob-util'가 아닌 'react-native-fs' 라이브러리를 활용해 해결해 보고자 아래의 공식문서를 참고하여 'react-native-fs' 사용법에 대해 정리해보고자 한다.
 
https://github.com/itinance/react-native-fs

GitHub - itinance/react-native-fs: Native filesystem access for react-native

Native filesystem access for react-native. Contribute to itinance/react-native-fs development by creating an account on GitHub.

github.com

 
 
 
 

react-native-fs 라이브러리란?

 
react-native-fs 라이브러리는 react-native환경에서 파일 시스템에 접근하여 파일과 디렉토리를 관리할 수 있도록 도와주는 라이브러리이다. 이 라이브러리를 통해 react-native환경에서 파일 읽기, 파일 쓰기, 파일 삭제, 디렉토리 생성 및 삭제 등의 기능을 수행할 수 있다.
 
 
 
 

react-native-fs 라이브러리의 특징들

 
다양한 파일 작업 기능들을 지원: 파일을 생성하고, 읽고, 쓰며, 삭제하는 기능들 뿐만 아니라, 파일이름의 변경과 파일 위치 이동, 파일크기나 생성날짜들을 조회하는 기능까지 다양한 작업들을 지원해 준다.
 
● 파일 경로접근의 용이성: DocumentDirectoryPath, TemporaryDirectoryPath와 같은 속성들을 활용해 파일의 경로들을 쉽게 접근할 수 있도록 지원해 준다.
 
대용향 파일처리 기능: 대용량 파일을 처리하기 위한 Stream 기능을 지원하기에 큰 사이즈의 파일들을 효율적으로 읽고 쓸 수 있고, 이를 통해 메모리 사용을 최적화할 수 있다.
 
● URL을 통한 다운로드와 업로드 기능: URL로부터 직접 파일을 다운로드하거나 업로드할 수 있을 기능들을 제공해 주며, 이를 통해 앱 내에서 resource들을 서버로부터 받아오거나 서버에 파일을 전송할 때 유용하게 사용된다.
 
백그라운드 작업 기능: 파일 다운로드나 업로드를 백그라운드에서 할 수 있도록 지원해 주기에 앱의 UI를 멈추지 않고 파일 작업들을 수행할 수 있음
 
 
 
 
 

react-native-fs 주요 메서들

● writeFile (filePath, contents, encoding-type): filePath에 지정된 경로에 contents내용을 담아 파일을 작성해 주는 메서드이며, encoding-type은 선택적으로 작성할 수 있고, 기본값은 'UTF8'이다.
 
● readFile (filePath, encoding-type): filePath에 지정된 경로에 있는 파일의 내용을 읽어서 문자열로 반환해 주는 메서드이며, encoding-type은 선택적으로 작성할 수 있고, 기본값은 'UTF8'이다.
 
● appendFile (filePath, contents, encoding-type ): filePath에 지정된 경로에 있는 기존 파일에 내용을 추가해 주는 메서드이며,  encoding-type은 선택적으로 작성할 수 있고, 기본값은 'UTF8'이다.
 
write (filePath, contents, position, encoding-type ): filePath에 지정된 경로에 있는 파일의 특정 위치(position 파라미)에 데이터를 작성해 주는 메서드이며, encoding-type은 선택적으로 작성할 수 있고, 기본값은 'UTF8'이다. 주로 파일 내의 특정 부분을 수정할 때 유용하게 쓰인다.
 
● unlink (filePath):  filePath에 지정된 경로에 있는 파일을 삭제시켜 주는 메서드이다.
 
moveFile (srcPath, destPath): srcPath경로에 있는 파일을 destPath경로의 위치로 이동시켜 주는 메서드이다.
 
 copyFile (srcPath, destPath): srcPath경로에 있는 파일을 destPath경로로 복제시켜 주는 메서드이다.
 
mkdir (dirPath): 새 디렉토리를 생성시켜 주는 메서드이다.
 
rmdir (dirPath): dir경로에 있는 디렉토리를 삭제시켜 주는 메서드이다.
 
● readdir (dirPath): 디렉토리 내의 파일 및 서브디렉터리 목록을 반환시켜 주는 메서드이다.
 
● downloadFile (options): 원격 서버에서 파일을 다운로드하는 메서드이다. options 객체는 fromUrl, toFile, headers, background, discretionary, cacheable 등의 속성들을 설정할 수 있다.
 
● uploadFiles (options): 하나 이상의 파일을 서버로 업로드시켜 주는 메서드이다. options 객체는 toUrl, files, headers, fields, method 등의 속성들을 설정할 수 있다.
 
● stat (filePath): 파일 또는 디렉토리의 속성들을 조회하는 메서드이다. (크기, 수정된 시간, 생성 시간 등)
 
 
 
 
 

경로지정과 관련된 여러 속성들 ( 안드로이드 환경)

 
● CachesDirectoryPath:  캐시 디렉토리의 경로로, 일시적으로 데이터를 저장하고자 할 때 사용되는 경로이다.
 
● DocumentDirectoryPath: 문서 디렉토리의 경로로, 클라이언트 측 데이터나 애플리케이션 관련 문서들을 저장하는 데 사용되는 경로이다.
 
● ExternalDirectoryPath: 외부 저장소에 대한 경로이다.
 
● ExternalStorageDirectoryPath: 외부 저장소의 디렉토리에 대한 경로로, 외부 SD카드와 같은 저장소에 접근할 때 주로 사용된다.
 
 
 
 

react-native-fs 사용법 (Windows 버전)

 

react-native-fs 다운로드

아래 명령어를 입력하여 react-native-fs 라이브러리를 다운로드하여주자. (react-native link는 RN 0.60 버전 이상부터는 자동으로 해줌, 저는 0.74 버전을 사용 중이기에 따로 설정하지 않았습니다.)
 

npm install react-native-fs

 
 
 

파일 접근 권한 설정

 
[ android - app - src - main - AndroidManifest.xml] 경로로 들어가 아래코드를 추가하여 SD카드, 앨범 등과 같은 외부 저장소 사용에 대한 권한을 주도록 하자.
 

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

 
 

react-native-fs 라이브러리 module 세팅

 
 
[android - settings.gradle] 경로로 들어가 ' settings.gradle' 파일에 아래의 코드를 추가해 react-native-fs 라이브러리 모듈을 현재 프로젝트에서 인식할 수 있게 해 주자.
 

...
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android')

 
 
그다음, [android - app - build.gradle] 경로로 들어가 아래 코드를 추가해 의존성에 해당 라이브러리를 추가하고 앱  빌드과정에 해당 라이브러리를 포함되게 만들어주자.
 

...
dependencies {
    ...
    implementation project(':react-native-fs')			// 이부분 추가
}

 
 
위와 같이 'react-native-fs' 라이브러리에 대한 모듈 설정과 외부 저장소에 대한 접근 권한, 의존성  세팅을 맞췄다면, 공식문서에 나와있는 버전대로 react native환경에서 안드로이드 네이티브에 최적화된 기능들을 쓸 수 있도록 패키지들을 추가하는 코드를 작성해 주자. (react-native 0.29.0 버전 이상은 아래 나와있는 걸 그대로 사용하면 됩니다.)
 
 

import com.rnfs.RNFSPackage;

public class MainApplication extends Application implements ReactApplication {
   // ...
    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
        new MainReactPackage(),
        new RNFSPackage() 
      );
    }

 
 
 
 

'react-native-fs' 라이브러리를 사용하기 위한 기본적인 세팅을 마쳤으니 이제 본격적으로 파일을 관리하기 위해 이 라이브러리를 사용하는 방법에 대해 정리해 보도록 하겠다.
 
 
 
 

파일 쓰기 기본 문법

 

import RNFS from 'react-native-fs';

const path = RNFS.DocumentDirectoryPath + '/test.txt';	//DocumentDirectoryPath를 활용해 test.txt로 파일경로 설정

const writeFile = async () => { 	 // 파일 쓰기 함수
    try {
      await RNFS.writeFile(path, '파일내용 입력', 'utf8');
    } catch (error) {
      console.error('파일 작성 도중 발생하는 에러 예외처리, error: ', error);
    }
};

 
 
 
 

파일 읽기 기본 문법

 

import RNFS from 'react-native-fs';

const path = RNFS.DocumentDirectoryPath + '/test.txt';	//DocumentDirectoryPath를 활용해 test.txt로 파일경로 설정

const writeFile = async () => { 	 // 파일 읽기 함수
    try {
      const content = await RNFS.readFile(path, 'utf8');
    } catch (error) {
      console.error('파일 읽는 도중 발생하는 에러 예외처리, error: ', error);
    }
};

 
 
 
 

파일 삭제 기본 문법

 

import RNFS from 'react-native-fs';

const path = RNFS.DocumentDirectoryPath + '/test.txt';	//DocumentDirectoryPath를 활용해 test.txt로 파일경로 설정

const deleteFile  = async () => { 	 // 파일 삭제 함수
    try {
      await RNFS.unlink(path);
    } catch (error) {
      console.error('파일 삭제 도중 발생하는 에러 예외처리, error: ', error);
    }
};

 
 
 
 

이미지 파일 저장 기본 문법

 

import RNFS from 'react-native-fs';

const imagePath = RNFS.DocumentDirectoryPath + '/test.jpg'; // DocumentDirectoryPath를 활용해 test.jpg로 파일경로 설정

const saveImage = async (imageUri) => {				// 이미지 파일 저장 함수
  try {
    await RNFS.copyFile(imageUri, imagePath);
  } catch (error) {
    console.error('이미지 저장 도중 발생하는 에러 예외처리, error: ', error);
  }
};

 
 
 
 

이미지 파일 읽기 기본 문법

 
 

import RNFS from 'react-native-fs';

const imagePath = RNFS.DocumentDirectoryPath + '/test.jpg'; // DocumentDirectoryPath를 활용해 test.jpg로 파일경로 설정

const loadImage = async () => {						// 이미지 파일 읽기 함수
  try {
    const fileContents = await RNFS.readFile(imagePath, 'base64');
    console.log('이미지 파일 내용: ', fileContents);
    return `data:image/jpeg;base64,${fileContents}`;
  } catch (error) {
    console.error('이미지 읽기 도중 발생하는 에러 예외처리, error: ', error);
  }
};

 
 
 
 
 

아래는 현재 작업 중인 파일에서 테스트파일을 만들어 image-picker로 선택한 이미지를 파일시스템에 저장한 다음 렌더링할 때 해당 이미지를 불러와본 예제이다. (이 예제는 추후 서버로 전송하는 것까지 완료된다면 관련 글을 작성할 것 이기에 해당글에서는 자세히 설명하지 않겠다.)

 
 
 

import React, {Component, useState} from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, TextInput, Platform } from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import axios from 'axios';
import { getToken, refreshAccessToken } from './token'
import RNFS from 'react-native-fs';
import RNFetchBlob from 'react-native-blob-util';

class AxiosTest extends Component {

    state = {
        imageUri: [],
        imageType: null, 
        imageName: null,
    }

    postHouseData = () => {
      this.state.imageUri.forEach((uri, index) => {
          RNFS.stat(uri)
              .then((stats) => {
                  console.log(`Image ${index}:`, stats);
                  if (stats.isFile()) {
                      console.log(`파일이 저장된 Uri: ${uri}`);
                      console.log(`파일크기: ${stats.size} bytes`);
                      console.log(`최근 수정일: ${stats.mtime}`);
                      console.log(`파일 존재여부: ${stats.isFile()}`);
                  }
              })
              .catch((error) => {
                  console.error(`Error retrieving file stats for URI ${uri}:`, error);
              });
      });
  };
  

    addImage = () => {
        const options = {
            mediaType: 'photo',
            quality: 1, 
            maxWidth: 300, 
            maxHeight: 300, 
            includeBase64: false, 
        }
        launchImageLibrary(options, response => {
                if (response.didCancel) {
                    console.log('사용자가 ImagaPicker를 취소했습니다.');
                } else if (response.error) {
                    console.log('ImagePicker내에서 에러가 발생했습니다: ', response.error);
                } else if (response.customButton) {
                    console.log('사용자가 custom버튼을 눌렀습니다: ', response.customButton);
                } else {

                    const { uri, type, fileName } = response.assets[0];
                    const newFilePath = `${RNFS.TemporaryDirectoryPath}/${fileName}`;
                    const fileUri = `file://${newFilePath}`
                    console.log(`Uri: ${uri}\ \n Type: ${type} \n Name: ${fileName} \n fileUri: ${fileUri}`);
                    
                    RNFS.copyFile(uri, newFilePath)
                    .then(() => {
                      this.setState(prevState => ({
                          imageUri: [...prevState.imageUri, fileUri], 
                          imageType: type,
                          imageName: fileName,
                      }));
                    })
                    .catch(err => console.error('File Copy Error:', err));
                  };
                  // console.log(`Uri: ${this.state.imageUri}, newFilePath: ${newFilePath}, Type: ${this.state.imageType}, Name: ${this.state.imageName}`);
            });
    };
    

  render() {

    return (
            <View style={styles.container}>
                <View style={styles.houseIMGView}>
                    <TouchableOpacity style={styles.ModifySelectView} onPress={this.addImage}>
                        <Text style={styles.InfoModify}> 사진 추가 </Text>
                    </TouchableOpacity>
                <ScrollView style={styles.addHouseIMGView}  
                    showsHorizontalScrollIndicator={false}  
                    horizontal={true}>
                        {this.state.imageUri.length > 0 ? (
                            this.state.imageUri.map((uri, index) => (
                                <Image 
                                    key={index}
                                    style={styles.houseIMG} 
                                    source={{ uri: uri }}  
                                />
                            ))
                        ) : (
                            <Text>이미지가 아직 로드되지 않았습니다.</Text>
                        )}
                </ScrollView>
                </View>
                <TouchableOpacity style={styles.reservationBtn} onPress={() => this.postHouseData()}>
                    <Text style={styles.InfoModify}> 숙소 등록하기</Text>
                </TouchableOpacity>
            </View>
    )
  }
}

// 스타일 시트
const styles = StyleSheet.create({
    container : {                
        alignItems: 'center', 
        justifyContent: "center",
        height: '100%',
    },
    houseIMGView : {
        alignItems: 'center', 
        justifyContent: "center",
        width: "96%",
        // backgroundColor: 'gray',
    },
    InfoModify: {
        fontSize: 30,
        margin: 40,
    },
    houseIMG: {
        height: 200,
        width: 200,
    },
});

export default AxiosTest;

 
 
 

반응형