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()
}
})
}
Related
I have an implementation of DownloadManager that works correctly on most devices I've tested it. I recently started testing on a Samsung Galaxy S10 (Android 9), and I noticed a completely different behavior. the queued download takes up to 10 min to even start, I can see this because my download's request visibility is VISIBILITY_VISIBLE_NOTIFY_COMPLETED so it shows up as a notification after several min of the download request being queued.
When the download completes (either failing or succeeding) I also noticed that I don't get a call to my registered BroadcastReceiver until probably other 10 min, I know iths finished because, it's visible on the notifications section of the OS.
Has anyone faced this or know how to make DownloadManager behave as its expected?
I've considered rewriting the component so I don't depend on DownloadManager but that exactly what Im trying to avoid.
This is my implementation:
Permissions:
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE " />
DownloadManager implementation
/**
* Manages all DB retrieval and file level validation.
*
*/
class DataBaseInitializationRepository(private val app: Application, private val dbHelper: DatabaseFileHelper) {
fun initiateDB( callback: DownloadCompleteCallback){
val dbUrl = dbHelper.getDbUrl()
val tempDbFile = dbHelper.getTempDbFile()
val permanentDbFile = dbHelper.getPermanentDbFile()
if (!permanentDbFile.exists() && tempDbFile.length() <= 0) {
val downloadManager = app.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downLoadDBRequest = DownloadManager.Request(Uri.parse( dbUrl ))
.setTitle( app.getString( R.string.download_db_title ) )
.setDescription(app.getString( R.string.download_db_description ))
.setDestinationInExternalFilesDir( app,
null,
tempDbFile.path
)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val downloadId = downloadManager.enqueue( downLoadDBRequest )
registerReceiver( callback, downloadId )
}else{
callback.onComplete(0L)
}
}
private fun registerReceiver(callback: DownloadCompleteCallback, downloadId: Long){
val receiver = object: BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (id == downloadId){
//Move index reads to reusable function
val downloadManager = app.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val query = DownloadManager.Query()
query.setFilterById( downloadId )
val data = downloadManager.query( query )
if(data.moveToFirst() && data.count > 0){
val statusIndex = data.getColumnIndex(DownloadManager.COLUMN_STATUS)
val status = data.getInt( statusIndex )
if(status == DownloadManager.STATUS_SUCCESSFUL){
val localUriIndex = data.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
val localFile = File(
data.getString( localUriIndex )
.replace("file://","" )
)
if(localFile.exists()){
permanentlyStoreDb(localFile)
callback.onComplete(id)
}else{
callback.onFail("Initial Database Download Failed - File not found")
}
}else if(status == DownloadManager.STATUS_FAILED){
val reasonIndex = data.getColumnIndex(DownloadManager.COLUMN_REASON)
val reason = data.getInt( reasonIndex )
if(reason == DownloadManager.ERROR_FILE_ALREADY_EXISTS){
callback.onComplete(id)
}else{
callback.onFail("Initial Database Download Failed: $reason")
}
}
}else{
callback.onFail("Initial Database Download Failed - Unable to read download metadata")
}
}
}
}
app.registerReceiver( receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) )
}
private fun permanentlyStoreDb(tempFile: File): File {
val permanentDbFile = dbHelper.getPermanentDbFile()
try {
if(tempFile.exists()) {
tempFile.copyTo(
permanentDbFile,
true,
1024
)
tempFile.delete()
}else{
throw IOException("Temporal DB file doesn't exist")
}
}catch (ioex: IOException) {
throw IOException("Unable to copy DB to permanent storage:", ioex)
}
return permanentDbFile
}
/**
* Allows download completion to be notified back to the calling view model
*/
interface DownloadCompleteCallback{
fun onComplete(downloadId: Long)
fun onFail(message: String)
}
}
DatabaseFileHelper contains the logic to determine the temporary file, the permanent DB location and the DB URL where the download will happen. This is the logic I used for the temporary file:
fun getTempDbFile(): File {
return File.createTempFile(<FILE-LOCATION>, null, app.cacheDir)
}
Context:
My project is in the space of WiFi P2P/ WiFi Direct. I have found and followed some useful tutorials and questions and have a working app for sending Images between phones.
A socket is opened and an OutputStream sent to the other device. Device 2 receives the stream and has to assume the mime Type in order to compress and save to a file.*
EDIT: *Only has to compress for images, but needs to know the File type to save it to storage.
I would now like to open up this feature to transfer a file of any (common) type and have the other device happily receive it and stash it in the appropriate location (Or just all to Downloads might make it simpler).
My question:
What can I use to determine the MIME type for all (image, text, calendar events, contacts, videos) files coming in as an InputStream.
Current code attempts:
I have tried using BitmapFactory.decodeStream() to receive options and mimeType. This only works for Images.
guessContentTypeFromStream() hasn't worked at all and I think it might only be suitable for streaming from internet source?
Any clarifications/tips on how Storage access framework should be used here, or techniques to determine mimeType are appreciated!
Below is code used on the Receive side:
inner class RxClass: Thread() {
lateinit var socket: Socket
lateinit var serverSocket: ServerSocket
override fun run() {
try {
serverSocket = ServerSocket(8888)
socket = serverSocket.accept()
//If this line is reached, then a connection has been established between server and client
Log.d("RECEIVE", "Connection established")
lateinit var bitmap: Bitmap
lateinit var inputStream: InputStream
lateinit var outputStream: OutputStream
var mimeType: String? = null
lateinit var path: String
try {
inputStream = socket.getInputStream()
Log.d("RECEIVE", "got InputStream")
runOnUiThread { Toast.makeText(applicationContext, "Receiving file...", Toast.LENGTH_SHORT).show() }
//mimeType = guessContentTypeFromStream(inputStream)
val options = BitmapFactory.Options()
bitmap = BitmapFactory.decodeStream(inputStream, null, options)!! //TODO: this needs to be for all file types
mimeType = options.outMimeType
Log.d("RECEIVE", "Decoded input, Type: $mimeType")
} catch (e: IOException) {
e.printStackTrace()
}
when(mimeType){ //I was hoping that my attempt would let me know what MIME type was and then here I can act on it differently if neccessary
"image/jpeg" -> path = Environment.DIRECTORY_PICTURES
"image/jpg" -> path = Environment.DIRECTORY_PICTURES
"image/bmp" -> path = Environment.DIRECTORY_PICTURES
"image/gif" -> path = Environment.DIRECTORY_PICTURES
"image/png" -> path = Environment.DIRECTORY_PICTURES
"audio/mpeg" -> path = Environment.DIRECTORY_MUSIC
"audio/x-wav" -> path = Environment.DIRECTORY_MUSIC
"video/wav" -> path = Environment.DIRECTORY_MOVIES
"video/mp4" -> path = Environment.DIRECTORY_MOVIES
"text/plain" -> path = Environment.DIRECTORY_DOCUMENTS
"text/html" -> path = Environment.DIRECTORY_DOCUMENTS
"text/x-vcard" -> path = Environment.DIRECTORY_DOCUMENTS
"text/x-vcalendar" -> path = Environment.DIRECTORY_DOCUMENTS
null -> Log.d("RECEIVE", "MIMETYPE is NULL")
}
Log.d("RECEIVE", "Save file in location: $path")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver: ContentResolver = contentResolver
val contentValues = ContentValues()
//contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, name)
//contentValues.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
//contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, path)
val rxUri: Uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
val inputStream = socket.getInputStream()
outputStream = resolver.openOutputStream(rxUri)!!
Log.d("RECEIVE", "Sending File: $rxUri")
inputStream.copyTo(outputStream)
if(bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)) {
runOnUiThread { Toast.makeText(applicationContext, "File Saved", Toast.LENGTH_SHORT).show() }
}
Log.d("RECEIVE", "close streams")
outputStream.flush()
outputStream.close()
inputStream.close()
runOnUiThread {
ReceivedImage.setImageURI(rxUri)
}
} else { //Version less than Android Q is not implemented
}
} catch (e: IOException){
e.printStackTrace()
}
}
}
Useful links
https://developer.android.com/training/data-storage
https://developer.android.com/training/data-storage/shared/media#add-item
https://developer.android.com/reference/kotlin/android/graphics/BitmapFactory
Thanks in advance
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
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);
In my phone's Storage, There is a file which is "MyFile.sql". There are 200 records at the file. What should I do is to import those 200 records into the app.
First, I just Initialize
llUpdate.setOnClickListener { UpgradeDB(txtUpdate!!).execute("", "", "") }
After that, I start a method, I don't know why It found the file and read already, But it doesn't import to the app. Is this because I write return = null So it didn't import to the app?
override fun doInBackground(vararg params: String): String? {
val filename = "MyFile.sql"
val sdcard = Environment.getExternalStorageDirectory()
val file = File(sdcard, filename)
if (!file.exists()) isCancelled
var dbHelper: MyDBHelper? = null
dbHelper?.writableDatabase.use { db ->
var intTotalLine = 0
var intLine = 1
BufferedReader(FileReader(file)).useLines { _ -> intTotalLine++ }
BufferedReader(FileReader(file)).use { r ->
r.lineSequence().forEach {
if (it.isNotEmpty()) {
db?.execSQL(it)
publishProgress(String.format("Updating %s/%s records", intLine, intTotalLine))
intLine++
}
}
}
}
return null
}
Can you guys Please Help me to check where are the mistakes? Thanks in advance.