[Flutter] 안드로이드 13+ 파일 쓰기 권한 문제 해결법

ParkHyunWoo·2023년 11월 12일

Flutter_Study

목록 보기
1/2

이 글은 안드로이드 13(API 33)에서 발생하는 쓰기 권한 문제에 대한 해결 과정으로 코드만 필요하신분은 아래로 내려가주세요.

(한국어로된 해결방법을 찾을 수가 없었는데 다른 한국인 개발자분들에게 이 글이 도움이 되면 좋겠네요)




Flutter 프로젝트를 진행하던 도중 Uint8List 형식의 이미지파일을 jpg 파일로 디바이스의 Download 폴더에 저장하는 코드를 작성하였으나 아래와 같이 오류가 발생하는 것을 확인하였다.

오류코드에서 대강 OS Error, 권한 관련으로 나오는 것을 보고 혹시나하여 테스트 환경을 바꿔보기로 했다.

Pixel 6 Pro(Android13)에서 Pixel 2 XL(Android 10.0)으로 바꿧더니 정상작동 하는 것을 확인하였다.

최신 안드로이드 빌드에서 발생하는 문제임을 확인하고 구글링을 해본 결과 안드로이드의 경우 13(API 33)에서부터 Write, Read external storage 권한이 완전히 삭제되었다는 것을 확인하였다.


출처 : https://pub.dev/packages/permission_handler (Permission_handler 패키지의 FAQ 페이지)

이를 해결하기 위해 관련 자료를 찾아보아도 한국 웹에선 아예 정보가 없을 뿐더러

스택오버플로우 등 해외 포럼에서 확인한 여러 외부 패키지를 시도해보았지만 해결되지 않았다.

그러던 와중 cr_file_saver라는 패키지를 발견하였고 설명문을 본 결과 API 30 이상에서 지원이 가능한 메소드가 있다는 것을 확인하였다.

출처 : https://pub.dev/packages/cr_file_saver (cr_file_saver 패키지의 Usage 페이지)

위 패키지의 saveFileWithDialog 메소드의 동작 방식을 살펴보면 먼저 매개변수(parameter)로 SaveFileDialogParams가 필요하고

이 SaveFileDialogParams는 sourceFilePath(저장에 사용할 원본 파일 위치), destinationFileName(새로 저장할 파일 이름) 두 매개변수가 필요하다.

어떻게 사용하는지 간단하게 설명하기 전에 먼저 현재 안드로이드 13에서 앱 내부 폴더(/data/user/0/패키지명/app_flutter)에는 문제없이 저장할 수 있다.

이를 이용하여 먼저 앱 내부 폴더에 임시로 파일(현재 경우엔 사진)을 저장 후 이 임시 파일의 위치를 sourceFilePath 변수로 넣어 주는 방식을 사용했다.

파일 이름은 destinationFileName에 본인이 원하는 대로 확장자와 함께 입력하면 된다. (예시: 'temp.jpg')

그 후 임시 파일을 삭제하기 위해 File().delete() 함수를 사용해주면 디바이스에 파일을 저장하는 코드가 완성이 된다. (임시 파일을 삭제해주지 않아도 되지만 사용하지 않는 더미파일이므로 삭제하는 편을 권장)

최종적으로 플러터 코드 및 실행 결과는 아래와 같다.

  Directory appDocDirectory = await getApplicationDocumentsDirectory(); 
  //앱 내부 폴더 경로를 get, path_provider 패키지 사용
  var temp_name = appDocDirectory.path + '/' + '${DateTime.now()}.jpg';
  File(temp_name).writeAsBytes(uint).then((value) async { //App폴더에 사진을 임시 저장후
    print(value);
    print('start process');
    final file = await CRFileSaver.saveFileWithDialog(
      SaveFileDialogParams(
          sourceFilePath: temp_name, //App폴더에 저장한 임시 사진을 Source로 이용
          destinationFileName: '${DateTime.now()}.jpg'
      ),
    ).then((value2) {
      print(value2);
      File(temp_name).delete();
      //App폴더안에 임시 저장한 사진 삭제
    });
    print('end process');
  });

위와 같이 cr_file_saver, path_provider 두 패키지를 이용한 방법 이외에도 다른 간단한 방법이 있을 수도 있겠지만 필자는 이 방식을 사용하였다.

플러터 개발자분들에게 이 글이 도움이 됬으면 좋겠습니다~


박현우(パクヒョンウ)
iu4148@gmail.com
https://github.com/KensukePark


0개의 댓글