Thanks for contributing iCantC on
Step 2: Save the image in Q style.
I ran into some issues with memory usage in Android Studio, which I had to open Sublime to fix. To fix this error:
e: java.lang.OutOfMemoryError: Java heap space
This is the code I used as my use case is for PNG images, any value of bitmap.compress
less than 100 is likely not useful.
Previous version would not work on API 30 so I updated contentValues RELATIVE_PATH
to DIRECTORY_DCIM
also contentResolver.insert(EXTERNAL_CONTENT_URI, ...
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z", Locale.getDefault()
)
private val legacyOrQ: (Bitmap) -> Uri = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
saveImageInQ(it) else legacySave(it) }
private fun saveImageInQ(bitmap: Bitmap): Uri {
val filename = "${title}_of_${dateFormatter.format(Date())}.png"
val fos: OutputStream?
val contentValues = ContentValues().apply {
put(DISPLAY_NAME, filename)
put(MIME_TYPE, "image/png")
put(RELATIVE_PATH, DIRECTORY_DCIM)
put(IS_PENDING, 1)
}
//use application context to get contentResolver
val contentResolver = applicationContext.contentResolver
val uri = contentResolver.insert(EXTERNAL_CONTENT_URI, contentValues)
uri?.let { contentResolver.openOutputStream(it) }.also { fos = it }
fos?.use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
fos?.flush()
fos?.close()
contentValues.clear()
contentValues.put(IS_PENDING, 0)
uri?.let {
contentResolver.update(it, contentValues, null, null)
}
return uri!!
}
Step 3: If not on Q save the image in legacy style
private fun legacySave(bitmap: Bitmap): Uri {
val appContext = applicationContext
val filename = "${title}_of_${dateFormatter.format(Date())}.png"
val directory = getExternalStoragePublicDirectory(DIRECTORY_PICTURES)
val file = File(directory, filename)
val outStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream)
outStream.flush()
outStream.close()
MediaScannerConnection.scanFile(appContext, arrayOf(file.absolutePath),
null, null)
return FileProvider.getUriForFile(appContext, "${appContext.packageName}.provider",
file)
}
Step 4: Create a custom FileProvider
package com.example.background.workers.provider
import androidx.core.content.FileProvider
class WorkerFileProvider : FileProvider() {
}
step 5: update your AndroidManifest.xml
changes from
<activity android:name=".MyActivity" />
to
<activity android:name=".MyActivity">
<intent-filter>
<action android:name="android.intent.action.PICK"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.OPENABLE"/>
<data android:mimeType="image/png"/>
</intent-filter>
</activity>
<provider
android:name=".workers.provider.WorkerFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
</intent-filter>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
step 6: add a resource under xml for FILE_PROVIDER_PATHS
in my case I needed the pictures folder
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="pictures" path="Pictures"/>
</paths>
insert()
with aContentValues
to get aUri
that you can use for writing out your content. For theIS_PENDING
stuff, yourinsert()
call would haveIS_PENDING
set to1
. Then, after you write out the content, you wouldupdate()
the item withIS_PENDING
set to0
. See this code snippet for an example, though in my case I am saving a video, not an image. – Insalivateuri?.let { resolver.openOutputStream(uri)?.use { outputStream -> val sink = Okio.buffer(Okio.sink(outputStream)) response.body()?.source()?.let { sink.writeAll(it) } sink.close() }
– Hypercorrectionresponse.body().source()
gives me an OkioSource
representing the bytes of the video that I am downloading.Okio.buffer(Okio.sink(outputStream))
gives me an OkioSink
representing where I am writing the bytes to, andwriteAll()
writes all the bytes from theSource
to theSink
. See this SO answer for the Square-approved approach. – Insalivate