How to save pdf in scoped storage?
Asked Answered
D

3

7

Before the introduction of scoped storage i was using Download Manager to download pdf in my app and get the pdf from getExternalStorageDirectory, but due to scoped storage i can no longer use getExternalStorageDirectory as it is deprecated. I decided to move away from Download Manager as well as it downloads files in public directory and instead use retrofit to download pdf file. I know i can use the requiredLegacyStorage tag in Android Manifest but it wont be applicable to Android 11 so i am not using that.

Here is my code

fun readAndDownloadFile(context: Context) {
        readQuraanInterface?.downloadFile()
        Coroutines.io {
            file = File(context.filesDir,"$DESTINATION_DIRECTORY/$FILE_NAME$FILE_EXTENSION")
            if (file?.exists() == true) {
                renderPDF()
                showPdf(mPageIndex, Direction.None)
            } else {

                Log.i("new","new0")
                val response = readQuraanRepository.downloadPdf()
                if (response.isSuccessful) {
                    Log.i("new","new00 ${file!!.path} ${response.body()?.byteStream().toString()}")
                    response.body()?.byteStream()?.let {
                        file!!.copyInputStreamToFile(
                            it
                        )
                    }
                    Log.i("new","new1")
//                    renderPDF()
//                    showPdf(mPageIndex, Direction.None)
                } else {
                    Log.i("new","new2")
                    Coroutines.main {
                        response.errorBody()?.string()
                            ?.let { readQuraanInterface?.downloadFailed(it) }
                    }
                }
            }

        }

    }

    private fun File.copyInputStreamToFile(inputStream: InputStream) {
        this.outputStream().use { fileOut ->
            Log.i("new","new30")
            inputStream.copyTo(fileOut)
        }
    }

Though the pdf id downloaded but the file is never stored using InputStream helper function which i have written. I need to add that pdf to my app's internal storage as well as render it which i am rendering using PDFRenderer.

Drum answered 9/5, 2020 at 6:48 Comment(5)
How do you check if the file is stored? If it is not stored there would be an error/exception.Coup
renderPDF()? Why doesnt that function have a path parameter?Coup
@Coup As soon as the pdf is downloaded my app crashes. It is not able to create the fileLurlenelurline
You did not answer my question. Strange. It will crash because you do not catch exceptions. The logcat will tell you the problem.Coup
@Coup The logcat does not show any errors. The app does not exactly crash. It crashes but it recovers automaticallyDrum
P
5

You can use below code to download and save PDF using scoped storage. Here I am using Downloads directory. Don't forget to give required permissions.

@RequiresApi(Build.VERSION_CODES.Q)
fun downloadPdfWithMediaStore() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val url =
                URL("https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")
            val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
            connection.requestMethod = "GET"
            connection.doOutput = true
            connection.connect()
            val pdfInputStream: InputStream = connection.inputStream

            val values = ContentValues().apply {
                put(MediaStore.Downloads.DISPLAY_NAME, "test")
                put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
                put(MediaStore.Downloads.IS_PENDING, 1)
            }

            val resolver = context.contentResolver

            val collection =
                MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

            val itemUri = resolver.insert(collection, values)

            if (itemUri != null) {
                resolver.openFileDescriptor(itemUri, "w").use { parcelFileDescriptor ->
                    ParcelFileDescriptor.AutoCloseOutputStream(parcelFileDescriptor)
                        .write(pdfInputStream.readBytes())
                }
                values.clear()
                values.put(MediaStore.Downloads.IS_PENDING, 0)
                resolver.update(itemUri, values, null, null)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
Propel answered 19/5, 2020 at 7:59 Comment(3)
How to use this func for java?Hamsun
Any leads for xamarin.forms ?Acephalous
doesn't work android 11!! if I use customised URL with following errors downloadPdfWithMediaStore$1.invokeSuspend System.err: java.io.FileNotFoundException:Halimeda
L
4

It is a more clean solution if you save file with Retrofit dynamic Urls.

  1. Create Api
interface DownloadFileApi {

   @Streaming
   @GET
   suspend fun downloadFile(@Url fileUrl: String): Response<ResponseBody>
}

And you can create the instance like

 Retrofit.Builder()
         .baseUrl("http://localhost/") /* We use dynamic URL (@Url) the base URL will be ignored */
         .build()
         .create(DownloadFileApi::class.java)

NOTE: You need to set a valid baseUrl even if you don't consume it since it is required by the retrofit builder

  1. Save InputStream result in storage device (you can create a UseCase to do that)
class SaveInputStreamAsPdfFileOnDirectoryUseCase {

    /**
     * Create and save inputStream as a file in the indicated directory
     * the inputStream to save will be a PDF file with random UUID as name
     */
    suspend operator fun invoke(inputStream: InputStream, directory: File): File? {
        var outputFile: File? = null
        withContext(Dispatchers.IO) {
            try {
                val name = UUID.randomUUID().toString() + ".pdf"
                val outputDir = File(directory, "outputPath")
                outputFile = File(outputDir, name)
                makeDirIfShould(outputDir)
                val outputStream = FileOutputStream(outputFile, false)
                inputStream.use { fileOut -> fileOut.copyTo(outputStream) }
                outputStream.close()
            } catch (e: IOException) {
                // Something went wrong
            }
        }
        return outputFile
    }

    private fun makeDirIfShould(outputDir: File) {
        if (outputDir.exists().not()) {
            outputDir.mkdirs()
        }
    }
}
  1. Call the api and apply the use case :D
class DownloadFileRepository constructor(
    private val service: DownloadFileApi,
    private val saveInputStreamAsPdfFileOnDirectory: SaveInputStreamAsPdfFileOnDirectoryUseCase
) {

    /**
     * Download pdfUrl and save result as pdf file in the indicated directory
     *
     * @return Downloaded pdf file
     */
    suspend fun downloadFileIn(pdfUrl: String, directory: File): File? {
        val response = service.downloadFile(pdfUrl)
        val responseBody = responseToBody(response)
        return responseBody?.let { saveInputStreamAsFileOnDirectory(it.byteStream(), directory) }
    }
    
    fun responseToBody(response: Response<ResponseBody>): ResponseBody? {
        if (response.isSuccessful.not() || response.code() in 400..599) {
            return null
        }
        return response.body()
    }
}

NOTE: You can use ContextCompat.getExternalFilesDirs(applicationContext, "documents").firstOrNull() as save directory

Langtry answered 10/2, 2021 at 3:16 Comment(0)
A
0

I am using the below code with targeted API 30 and after downloading its saving on the internal Download directory

       DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));//url=The download url of file
                    request.setMimeType(mimetype);
                    //------------------------COOKIE!!------------------------
                    String cookies = CookieManager.getInstance().getCookie(url);
                    request.addRequestHeader("cookie", cookies);
                    //------------------------COOKIE!!------------------------
                    request.addRequestHeader("User-Agent", userAgent);
                    request.setDescription("Qawmi Library Downloading");//Description
                    request.setTitle(pdfFileName);//pdfFileName=String Name of Pdf file
                    request.allowScanningByMediaScanner();
                    request.setAllowedOverMetered(true);
                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
                        request.setDestinationInExternalPublicDir("/Qawmi Library"/*Custom directory name below api 29*/, pdfFileName);
                    } else {
//Higher then or equal api-29                        
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,"/"+pdfFileName);
                    }
                    DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                    dm.enqueue(request);
Asuncionasunder answered 9/2, 2021 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.