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.
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()
}
}
}
It is a more clean solution if you save file with Retrofit dynamic Urls.
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
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()
}
}
}
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
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);
Related
I am stuck in between a strange issue of uploading image file to server. Although I did upload file several times before, but this time I don't understand what is the issue.
I get the file path of respective file but RequestBody returns null. Below I mentioned what library I'm using.
I am using kotlin, MultiPart and RequestBody for file upload.
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
Below is my code which I wrote for file upload. In which you can see GalleryCameraUtility.getImageRequestBody(imageFile) returns null for file.
File path from mobile device /storage/emulated/0/DCIM/Screenshots/test_image.jpg
fun addNewCompany(companyName: String, email: String,imageFile: File, ownerName: String, address: String, companyDetails: String){
val companyNameBody: RequestBody = companyName.toRequestBody("text/plain".toMediaType())
val emailBody: RequestBody = email.toRequestBody("text/plain".toMediaType())
val fileData: RequestBody? = GalleryCameraUtility.getImageRequestBody(imageFile)
val ownerNameBody: RequestBody = ownerName.toRequestBody("text/plain".toMediaType())
val addressBody: RequestBody = address.toRequestBody("text/plain".toMediaType())
val userIdBody: RequestBody = PreferenceHelper.readUserIdPref(Constants.USER_ID).toString()
.toRequestBody("text/plain".toMediaType())
addCompanyRepo.addNewCompanyApi(companyNameBody, emailBody, fileData, ownerNameBody, addressBody, userIdBody)
}
class GalleryCameraUtility {
companion object{
fun getImageRequestBody(sourceFile: File) : RequestBody? {
var requestBody: RequestBody? = null
Thread {
val mimeType = getMimeType(sourceFile);
if (mimeType == null) {
Log.e("file error", "Not able to get mime type")
return#Thread
}
try {
requestBody = sourceFile.path.toRequestBody("multipart/form-data".toMediaTypeOrNull())
/*MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart(serverImageKey, uploadedFileName,sourceFile.asRequestBody(mimeType.toMediaTypeOrNull()))
.build()*/
} catch (ex: Exception) {
ex.printStackTrace()
Log.e("File upload", "failed")
}
}.start()
return requestBody;
}
// url = file path or whatever suitable URL you want.
private fun getMimeType(file: File): String? {
var type: String? = null
val extension = MimeTypeMap.getFileExtensionFromUrl(file.path)
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
}
return type
}
}
}
I spent so many hours on this but not able to find solution. Please help me out on this.
Basically, I am trying to download three different images(bitmaps) from a URL and save them to Apps Internal storage, and then use the URI's from the saved file to save a new Entity to my database. I am having a lot of issues with running this in parallel and getting it to work properly. As ideally all three images would be downloaded, saved and URI's returned simultaneously. Most of my issues come from blocking calls that I cannot seem to avoid.
Here's all of the relevant code
private val okHttpClient: OkHttpClient = OkHttpClient()
suspend fun saveImageToDB(networkImageModel: CBImageNetworkModel): Result<Long> {
return withContext(Dispatchers.IO) {
try {
//Upload all three images to local storage
val edgesUri = this.async {
val req = Request.Builder().url(networkImageModel.edgesImageUrl).build()
val response = okHttpClient.newCall(req).execute() // BLOCKING
val btEdges = BitmapFactory.decodeStream(response.body?.byteStream())
return#async saveBitmapToAppStorage(btEdges, ImageType.EDGES)
}
val finalUri = this.async {
val urlFinal = URL(networkImageModel.finalImageUrl) // BLOCKING
val btFinal = BitmapFactory.decodeStream(urlFinal.openStream())
return#async saveBitmapToAppStorage(btFinal, ImageType.FINAL)
}
val labelUri = this.async {
val urlLabels = URL(networkImageModel.labelsImageUrl)
val btLabel = BitmapFactory.decodeStream(urlLabels.openStream())
return#async saveBitmapToAppStorage(btLabel, ImageType.LABELS)
}
awaitAll(edgesUri, finalUri, labelUri)
if(edgesUri.getCompleted() == null || finalUri.getCompleted() == null || labelUri.getCompleted() == null) {
return#withContext Result.failure(Exception("An image couldn't be saved"))
}
} catch (e: Exception) {
Result.failure<Long>(e)
}
try {
// Result.success( db.imageDao().insertImage(image))
Result.success(123) // A placeholder untill I actually get the URI's to create my Db Entity
} catch (e: Exception) {
Timber.e(e)
Result.failure(e)
}
}
}
//Save the bitmap and return Uri or null if failed
private fun saveBitmapToAppStorage(bitmap: Bitmap, imageType: ImageType): Uri? {
val type = when (imageType) {
ImageType.EDGES -> "edges"
ImageType.LABELS -> "labels"
ImageType.FINAL -> "final"
}
val filename = "img_" + System.currentTimeMillis().toString() + "_" + type
val file = File(context.filesDir, filename)
try {
val fos = file.outputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (e: Exception) {
Timber.e(e)
return null
}
return file.toUri()
}
Here I am calling this function
viewModelScope.launch {
val imageID = appRepository.saveImageToDB(imageNetworkModel)
withContext(Dispatchers.Main) {
val uri = Uri.parse("$PAINT_DEEPLINK/$imageID")
navManager.navigate(uri)
}
}
Another issue I am facing is returning the URI in the first place and handling errors. As if one of these parts fails, I'd like to cancel the whole thing and return Result.failure(), but I am unsure on how to achieve that. As returning null just seems meh, I'd much prefer to have an error message or something along those lines.
I have to open a PDF from a URL that requires an access token (The PDF is not publically available). The PDF should not be permanently stored on the device.
If the access token wouldn't be required, the solution would look like this:
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse(url), "application/pdf") }
startActivity(intent)
However, an access token is required and therefore this doesn't work.
I have tried downloading the file in the app and then opening the local copy of the file, like this:
val inputStream = /*get PDF as an InputStream*/
val file = File.createTempFile(
fileName,
".pdf",
context?.externalCacheDir
).apply { deleteOnExit() }
stream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.fromFile(file), "application/pdf") }
startActivity(intent)
but this gives me a android.os.FileUriExposedException with the message <myFilePath> exposed beyond app through Intent.getData().
I have also tried the same thing using context?.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) and got the same result.
So far, the closest I got was like this:
val inputStream = /*get PDF as an InputStream*/
val file = File.createTempFile(
fileName,
".pdf",
context?.filesDir
).apply { deleteOnExit() }
stream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse(file.absolutePath), "application/pdf") }
startActivity(intent)
Doing it like this, my Samsung device offered me Samsung Notes and Microsoft Word to open the PDF and neither of them was actually able to do it. (It just opened these apps and they showed an error that they couldn't find the file they were supposed to open)
The installed PDF reader - the one that would normally open the PDF when using my two lines of code at the very top of this post - wasn't even listed.
So, how do I properly show this PDF?
Edit:
After finding this answer, I added a file Provider and now my code looks like this:
val inputStream = /*get PDF as an InputStream*/
val file = File.createTempFile(
fileName,
".pdf",
context?.getExternalFilesDir(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
Environment.DIRECTORY_DOCUMENTS
else
Environment.DIRECTORY_DOWNLOADS
).apply { deleteOnExit() }
stream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
val documentUri: Uri = FileProvider.getUriForFile(
requireContext(),
getString(R.string.file_provider_authority),
file
)
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(documentUri, "application/pdf") }
startActivity(intent)
Now the PDF can be opened properly. However the PDF is empty.
This is how I load the PDF to an InputStream:
interface DocumentsClient {
#GET("/api/something/pdf/{pdf_id}")
#Headers("Accept: application/pdf")
#Streaming
fun loadSomethingPDF(#Path("pdf_id") pdfId: Long): Call<ResponseBody>
}
fun loadSomethingPDF(accessToken: String?, pdfId: Long) =
createDocumentsClient(accessToken).loadSomethingPDF(pdfId)
private fun createDocumentsClient(accessToken: String?): DocumentsClient {
val okClientBuilder = OkHttpClient.Builder()
// add headers
okClientBuilder.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
if (accessToken != null) {
requestBuilder.header("Authorization", "Bearer $accessToken")
}
val request = requestBuilder.build()
chain.proceed(request)
}
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okClientBuilder.build())
.build()
return retrofit.create(DocumentsClient::class.java)
}
loadSomethingPdf then gets called like this:
#Throws(IOException::class, IllegalStateException::class)
suspend fun loadSomethingPdf(accessToken: String?, pdfId: Long) = withContext(Dispatchers.IO) {
val call = remoteManager.loadSomethingPDF(accessToken, pdfId)
try {
val response = call.execute()
if (!response.isSuccessful) {
return#withContext null
}
return#withContext response.body()?.byteStream()
} catch (e: IOException) {
throw e
} catch (e: IllegalStateException) {
throw e
}
}
And that's where I get the inputStream from.
Ok, my problem was somewhere completely different.
I needed to grant Uri permissions to allow the other apps to read the Uri I am sending them.
The code just needed this:
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
I've been beating my head against this issue for quite awhile... I am updating an app that uses DownloadManger to do a simple task like downloading a file to the external storage public directory i.e:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
Everything works fine here from Android api 19-28. Its when testing on API 29 (Q/10) is where issues occur. Android implemented scoped storage and so deprecated the getExternalStoragePublicDirectory... As a result I need to figure out a compatible solution to support APIs 19-29. I cannot use internal application storage since DownloadManager will throw a SecurityException. Androids documentation states that I can use the DownloadManager.Request setDestinationUri and it even mentions for Android Q that I can use Context.getExternalFilesDir(String). When I do this though, the path is still the emulated path:
/storage/emulated/0/Android/data/com.my.package.name/files/Download/myFile.xml
I get a callback from the download manager that the download is complete (with right ID) but then I cannot grab the download from the area I saved it to. I check to see if the file exists and it returns false:
new File("/storage/emulated/0/Android/data/com.my.package.name/files/Download/myFile.xml").exists();
Any help is appreciated
Adding code for context. So setting up download manager
private void startDownload() {
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
registerReceiver(downloadReceiver, filter);
String remoteURL= getString(R.string.remote_url);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteUrl));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
request.setTitle(getString(R.string.download_title));
request.setDescription(getString(R.string.download_description));
request.setDestinationUri(Uri.fromFile(new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "myFile.xml")));
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
mainDownloadID= manager.enqueue(request);
}
checking file if it exists:
new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "myFile.xml").exists(); //this returns false in the onReceive (and download IDs match)
Try add this into your manifest file in application tag
android:requestLegacyExternalStorage="true"
File Paths outside of the App's private directories in Android Q and above useless.
See https://developer.android.com/training/data-storage#scoped-storage
You need to ask the user where to download the files too, this will get you a URI for the DownloadManager destination.
https://developer.android.com/training/data-storage/shared/documents-files#grant-access-directory
You will probably want to persist this permission
https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions
Yeah Its scope storage but even though you can download file in Q+ using downloadmanger no need to do android:requestLegacyExternalStorage="true"
I am doing this way.
manifest
-->
Downloadmanger
val fileName =
Constants.FILE_NAME + Date().time
val downloadUri = Uri.parse(media.url)
val request = DownloadManager.Request(
downloadUri
)
request.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
)
.setAllowedOverRoaming(true).setTitle("Some name")
.setDescription("Downloading file")
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS, File.separator + FOLDER + File.separator + fileName
)
Toast.makeText(
context,
"Download successfully to ${downloadUri?.path}",
Toast.LENGTH_LONG
).show()
downloadManager.enqueue(request)
Hence it will ask write permission below Q, but in Q and Q+ it will download without asking permission in /Download/folder dir.
Use this code and enjoy, this code uses RxJava for network call:
import android.content.ContentValues
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import io.reactivex.Observable
import io.reactivex.ObservableEmitter
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.ResponseBody
import java.io.*
import java.net.HttpURLConnection
import java.util.concurrent.TimeUnit
class FileDownloader(
private val context: Context,
private val url: String,
private val fileName: String
) {
private val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build()
private val errorMessage = "File couldn't be downloaded"
private val bufferLengthBytes: Int = 1024 * 4
fun download(): Observable<Int> {
return Observable.create<Int> { emitter ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // To Download File for Android 10 and above
val content = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val uri = context.contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
content
)
uri?.apply {
val responseBody = getResponseBody(url)
if (responseBody != null
) {
responseBody.byteStream().use { inputStream ->
context.contentResolver.openOutputStream(uri)?.use { fileOutStream ->
writeOutStream(
inStream = inputStream,
outStream = fileOutStream,
contentLength = responseBody.contentLength(),
emitter = emitter
)
}
emitter.onComplete()
}
} else {
emitter.onError(Throwable(errorMessage))
}
}
}
else { // For Android versions below than 10
val directory = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS).absolutePath
).apply {
if (!exists()) {
mkdir()
}
}
val file = File(directory, fileName)
val responseBody = getResponseBody(url)
if (responseBody != null) {
responseBody.byteStream().use { inputStream ->
file.outputStream().use { fileOutStream ->
writeOutStream(
inStream = inputStream,
outStream = fileOutStream,
contentLength = responseBody.contentLength(),
emitter = emitter
)
}
emitter.onComplete()
}
} else {
emitter.onError(Throwable(errorMessage))
}
}
}
}
private fun getResponseBody(url: String): ResponseBody? {
val response = okHttpClient.newCall(Request.Builder().url(url).build()).execute()
return if (response.code >= HttpURLConnection.HTTP_OK &&
response.code < HttpURLConnection.HTTP_MULT_CHOICE &&
response.body != null
)
response.body
else
null
}
private fun writeOutStream(
inStream: InputStream,
outStream: OutputStream,
contentLength: Long,
emitter: ObservableEmitter<Int>) {
var bytesCopied = 0
val buffer = ByteArray(bufferLengthBytes)
var bytes = inStream.read(buffer)
while (bytes >= 0) {
outStream.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = inStream.read(buffer)
// emitter.onNext(
((bytesCopied * 100) / contentLength).toInt()
// )
}
outStream.flush()
outStream.close()
}
}
On calling side you've to right this:
private fun downloadFileFromUrl(context: Context, url: String, fileName: String) {
FileDownloader(
context = context,
url = url,
fileName = fileName
).download()
.throttleFirst(2, TimeUnit.SECONDS)
.toFlowable(BackpressureStrategy.LATEST)
.subscribeOn(Schedulers.io())
.observeOn(mainThread())
.subscribe({
// onNext: Downloading in progress
}, { error ->
// onError: Download Error
requireContext()?.apply {
Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show()
}
}, {
// onComplete: Download Complete
requireContext()?.apply {
Toast.makeText(this, "File downloaded to Downloads Folder", Toast.LENGTH_SHORT).show()
}
})
}
I am downloading file from server ,and saving to download folder in android ,accessing file path by following way to save
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
is depreceated in Android Q ,So I have to use Storage access framework .
fun downloadFile(url: String, originalFileName: String) {
mDownloadDisposable.add(
mRCloudRepository.downloadFile(url)
.flatMap(object : Function1<Response<ResponseBody>, Flowable<File>> {
override fun invoke(p1: Response<ResponseBody>): Flowable<File> {
return Flowable.create({
try {
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absoluteFile, originalFileName)
val sink = file.sink().buffer()
sink.writeAll(p1.body()!!.source())
sink.close()
it.onNext(file)
it.onComplete()
} catch (e: IOException) {
e.printStackTrace()
it.onError(e)
}
}, BackpressureStrategy.BUFFER)
}
})
.subscribeOn(mIoScheduler)
.observeOn(mUiScheduler)
.subscribe(
{
cancelNotification()
val contentIntent: PendingIntent = PendingIntent.getActivity(mContext, NotificationBuilder.NOTIFICATION_DOWNLOAD_ID, getOpenFileIntent(it), PendingIntent.FLAG_CANCEL_CURRENT)
NotificationBuilder.showDownloadedNotification(mContext!!, 2, "Downloaded $originalFileName ", "", contentIntent)
}, {
cancelNotification()
it.printStackTrace()
}
)
)
}
You can try DocumentFileCompat#createDownloadWithMediaStoreFallback() in Simple Storage:
val uri = DocumentFileCompat.createDownloadWithMediaStoreFallback(applicationContext, FileDescription(filename))
val output = uri?.openOutputStream(applicationContext)
val input = // get input stream from Response<ResponseBody>
// then, write input stream to output stream
In Android 10, accessing files in Download directory requires URI, instead of direct file path.