목차

목적

PDF파일을 저장하기 위해서는, 당연히 파일을 저장하는 방법을 배워야 한다.

이하에서는 파일을 저장하는 코드 스닛펫에 대하여 다룬다.

안드로이드에서는 보안을 강화하기 위하여 직관적인 디렉토리 구조를 사용하지 않기 때문에,

단순한 JAVA의 FILE 문법으로는 파일을 저장할 수 없음을 염두에 두어야 한다.

파일 저장

일단 PDF파일을 저장하려면, 당연히 안드로이드에서 파일을 저장하여 내려받는 방법을 알아야 한다.

파일저장을 저장하여 다운로드 폴더에 내려 받는 방법은 동기화 방법과 비동기화 방법이 있다.

1. 동기화로 파일 저장하는 방법

전통적인 방법은 다음의 방식으로 파일을 저장한다.

val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(dir, "PDFAssault.pdf")
val fos = FileOutputStream(file)
fos.write("hey".toByteArray())
fos.close()

위의 코드에서 fileoutputstream부분을 pdfDocument의 아웃풋스트림으로 바꾸어 주면, 우리가 앞으로 만들 PDF Document API를 이용한 PDF 파일의 스트림을 파일로 저장하게 된다. 그 코드는 아래와 같다.

val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(dir, "PDFAssault.pdf")
val fos = FileOutputStream(file)
pdfDocument.writeTo(fos)
fos.close()

어찌되었건 제트팩컴퍼즈에서는 파일의 디렉토리를 getExternalStoragePublicDirectory로 설정해주어야지만 파일을 읽고 쓰는 권한이 생긴다는 것을 명심하자.

간혹 인터넷을 찾아보면, 외부저장소를 지정해주는 getExternalStoragePublicDirectory 없이 다음과 같은 코드를 쓰는 경우가 있는데, 이러한 코드는 현재의 안드로이드 버전에서는 작동이 되지 않는다. 백날 AnroidManifest.xml에 uses-permission 부분을 고쳐봤자 안되니 헛수고하지 말자. 다음은 현재의 안드로이드 버전에서는 독장하지 않는 파일 쓰기 코드이다.

val file: File = File(Environment.DIRECTORY_DOCUMENTS, "GFG.pdf")

이렇게 getExternalStoragePublicDirectory 함수 없이 쓰면 파일 권한이 없다고 나온다.

2. 비동기화로 파일을 저장하는 방법

rememberLauncherForActivityResult를 이용하여 코루틴으로 파일을 저장할 수 있다.

이 경우에는 파일을 저장한다고 알려주는 intent를 만들어야 한다.

다음이 코드 예시이다.

@Composable
fun FileDownloadDemo() {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
 
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult()
    ) { result ->
        val data = result.data
        val uri = data?.data
        uri?.let {
            context.contentResolver.openOutputStream(it)?.let { outputStream ->
                scope.launch {
                    withContext(Dispatchers.IO) {
                       // Utility.logDebug(msg = "outputStream: $outputStream")
                        outputStream.write("hey".toByteArray())
                        outputStream.flush()
                        outputStream.close()
                    }
                }
            }
        }
    }
 
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .fillMaxSize()
    ) {
        Button(onClick = {
            val intent = createNewDocumentIntent()
            launcher.launch(intent)
        }) {
            Text(text = "Add To Files")
        }
    }
}
 
fun createNewDocumentIntent(): Intent {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "test-${System.currentTimeMillis()}.txt")
    }
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
    intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
    return intent
}

결론

PDF파일을 만드는 것은 매우 순식간에 일어나므로 앞으로 동기화 방식을 이용할 것이다.

따라서 다음과 같은 함수를 만들어주면 좋다.

@Throws(IOException::class)
fun savePdfFileExternalStorage(filename: String, document: PdfDocument) {
    val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    val file = File(dir, filename)
    val fos = FileOutputStream(file)
    document.writeTo(fos)
    fos.close()
}