I have a Bitmap image that I want to save and have it show up in my gallery, unfortunately I am having a lot of issues trying to complete this task. Since yesterday I've probably tried a dozen different code snippets to do just this. I think what's happening is that these code snippets are working, but they're saving it in the application's data and not in the shared storage/gallery.
How do I re-adapt this code to have it store images in a folder that any gallery application or file manager can access and not just in the application's data?
private fun saveToGallery(bitmap: Bitmap) {
Toast.makeText(applicationContext,"Saving image!", Toast.LENGTH_SHORT).show()
var outputStream: FileOutputStream? = null
val file = Environment.getExternalStorageDirectory()
val dir = File(file.absolutePath.toString() + "/MyPics")
dir.mkdirs()
val filename = String.format("%d.png", System.currentTimeMillis())
val outFile = File(dir, filename)
try {
outputStream = FileOutputStream(outFile)
} catch (e: Exception) {
e.printStackTrace()
}
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
try {
outputStream!!.flush()
} catch (e: Exception) {
e.printStackTrace()
}
try {
outputStream!!.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
You should send a broadcast event after saving the image.
For example:
https://stackoverflow.com/a/39448816/5313283
Related
I save a png image to external storage using this block of code for sdk<=28
/**
* save image with this method if the sdk is 28 or lower
*/
private fun saveImageSdk28(fileName: String){
//declar the output stream variable outside of try/catch so that it can always be closed
var imageOutputStream: FileOutputStream? = null
var outputImageFile = getFile(fileName)
if (!outputImageFile.exists()) {
outputImageFile.createNewFile()
}
try {
imageOutputStream = FileOutputStream(outputImageFile)
encryptedBitmap.compress(Bitmap.CompressFormat.PNG, 100, imageOutputStream)
} catch (e: IOException) {
e.printStackTrace()
Timber.i(e.toString())
} finally {
if (imageOutputStream != null) {
imageOutputStream.flush()
imageOutputStream.close()
}
}
}
/**
* returns file from fileName
*/
fun getFile(fileName: String): File{
//open, or create the directory where the image will be stored
var directory = File(
Environment.getExternalStorageDirectory().toString() + "/AppNameOutput/"
)
if (!directory.exists()) {
directory.mkdir()
}
//create the file
var file: File = File(directory.absolutePath, fileName)
return file
}
and this code for when the sdk>28
/**
* save image with this method if the sdk is 29 or higher
*/
#RequiresApi(Build.VERSION_CODES.Q)
private fun saveImageSdk29(fileName: String){
val imageCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "$fileName")
put(MediaStore.Images.Media.MIME_TYPE, "image/png")
put(MediaStore.Images.Media.WIDTH, encryptedBitmap.width)
put(MediaStore.Images.Media.HEIGHT, encryptedBitmap.height)
}
try{
val contentResolver = getApplication<Application>().contentResolver
contentResolver.insert(imageCollection, contentValues)?.also {uri->
contentResolver.openOutputStream(uri).use {outputStream ->
encryptedBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
}
}
}catch (e: IOException){
e.printStackTrace()
}
}
The image sucsessfully saves on the users device and can be accesed through files, however, the user can't access these images through the gallery, or Images tab.
I solved it. Turns out you just need to wait a while and reboot the phone for the gallery to show your images.
I have a page in my application where the user select an image from the gallery and save it as part of needed details. The image will be sent to a server through API request using Ktor. I have tried to convert the image bitmap to file but I get "operation not permitted" exception, and tired to look for a solution but nothing worked. One of the solutions recommended to add use permission for storage in the Manifest:
It didn't work as well.
The code is too long but here the block where I need to convert and save the file:
fun bitmapToFile(bitmap: Bitmap, fileNameToSave: String): File? { // File name like "image.png"
//create a file to write bitmap data
var file: File? = null
return try {
file = File(Environment.DIRECTORY_PICTURES + File.separator + fileNameToSave)
file.createNewFile()
//Convert bitmap to byte array
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos) // YOU can also save it in JPEG
val bitmapData = bos.toByteArray()
//write the bytes in file
val fos = FileOutputStream(file)
fos.write(bitmapData)
fos.flush()
fos.close()
file
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
e.printStackTrace()
file // it will return null
}
}
fun handleChangeImage() {
try{
imageUri?.let {
if (Build.VERSION.SDK_INT < 28) {
bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, it)
} else {
val source = ImageDecoder.createSource(context.contentResolver, it)
bitmap = ImageDecoder.decodeBitmap(source)
}
}
//fixme: file conversion throw operation denied error
val fileName = imageUri?.lastPathSegment.toString().replace(":", ".")
val fileInput = if (bitmap != null)
bitmapToFile(bitmap!!, fileName)
else null
if(fileInput == null)
throw Exception("Error")
assetImageInput.value = fileInput
} catch (e: FileNotFoundException) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
}
}
After converting the image it should be saved as "assetImageInput.value" and be sent through the API as a parameter.
I'm trying to write a byteArray received from a server. This is my code
private fun writePdf(content: ByteArray) {
val storageDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
val file = File("${storageDir?.path}/", "${Date().time}Download.pdf")
try {
// file.writeBytes(archivo)
val os = FileOutputStream(file, false)
os.write(content)
os.flush()
os.close()
} catch (e: IOException) {
e.printStackTrace()
}
val intent = Intent(Intent.ACTION_VIEW)
val uri = FileProvider
.getUriForFile(
this,
this.packageName + ".fileprovider",
file)
intent.setDataAndType(uri, "application/pdf")
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
try{
startActivity(intent)
}catch (e: Exception){
e.printStackTrace()
Toast.makeText(this, "Error", Toast.LENGTH_LONG)
.show()
}
}
The problem is that when the pdf opens it is blank, like nothing has been written.
I've tried writing with FileOutputStream and File.writeBytes.
I've checked the byteArray (in case is corrupted or something) and it has no problems.
I've checked the length() of the file before and after writing, and it's length increases accordingly.
Thanks, any kind of help is appreciated.
I finally found the problem, and solution. Everything with the code above was okay; the problem was that the flags for the intent to visualize the PDF were wrong. Instead of:
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
It should be:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
Iam trying to open a saved pdf file from the internal cache dir.
In the "old" way my code looked liked this.
In jetpack compose i can use this part of code, the pdf is beeining created and i can see it in the device explorer. But how can i display the pdf on screen ?
fun decodeTestPdfString(pdf_string:String, context:Context) {
//make FileOutPutStream
var fos: FileOutputStream? = null
try {
if (pdf_string != null) {
f = File(context?.cacheDir, "testFile" + ".pdf")
f!!.createNewFile()
fos = FileOutputStream(f)
val decodedString: ByteArray = Base64.decode(pdf_string, Base64.DEFAULT)
fos.write(decodedString)
fos.flush()
fos.close()
}
} catch (e: Exception) {
} finally {
if (fos != null) {
fos = null
}
}
val path: Uri = FileProvider.getUriForFile(context!!, context!!.getApplicationContext().getPackageName() + ".provider", f!!)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(path, "application/pdf")
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
}
}
}
But the last step (try intent) is giving an issue.
The file is created in the cache dir, i can see it.
To open the pdf reader:
startActivity(context, intent, options:null)
my App collecting some data from the user including an optional picture. To getting a High-Res picture i'm using following code:
https://medium.com/codex/how-to-use-the-android-activity-result-api-for-selecting-and-taking-images-5dbcc3e6324b
Getting the picture works as expected. If the user click on a save button, all data shall be written to an CSV-File on the SD-Card, and if latestTmpUri not null the user made a picture as well, and should be saved to the SD-Card, also.
I tried some snippets to move a file on Android, but everytime i'll get an error "File not exists". Maybe it has to do with path in provider_paths.xml, but i'm not sure.
By the way, i'm newbee on programming in Kotlin for Android.
EDIT:
If you take a look in the code from the URL above, there is an deleteOnExit()
private fun getTmpFileUri(): Uri {
val tmpFile = File.createTempFile("tmp_image_file", ".png", cacheDir).apply {
createNewFile()
deleteOnExit()
}
return FileProvider.getUriForFile(applicationContext, "${BuildConfig.APPLICATION_ID}.provider", tmpFile)
}
And if you look in provider_paths.xml
<cache-path name="cached_files" path="." />
<files-path name="images" path="." />
This is the path of the picture
content://com.company.contacts.provider/cached_files/tmp_image_file580022157706292749.png
To give an other path in <cache-path name="cached_files" path="." /> is not the solution i guess, because the SD-CARD's got a unique identifier, like E534-12F6
After a bit of research and thinking about FileInputStream and FileOutputStream and reading this post
https://stackoverflow.com/a/11327789/10155771
i got my solution. Depending on the Code in my first post to take a High-Res picture i modified it in this way:
private lateinit var tmpFile: File
private fun getTmpFileUri(): Uri {
tmpFile = File.createTempFile("tmp_image_file", ".png", cacheDir).apply {
createNewFile()
deleteOnExit()
}
return FileProvider.getUriForFile(applicationContext, "${BuildConfig.APPLICATION_ID}.provider", tmpFile)
}
to make the variable tmpFile global.
In my function to save the CSV and the optional picture i did this:
var imageName = ""
if(latestTmpUri != null) { // There was taken a picture if not null
val folder = getExternalFilesDirs(Environment.DIRECTORY_PICTURES)
val root = java.lang.String.valueOf(folder[1]).toString() // folder[1] is my SD-Card while folder[0] is internal storage
val filets: String = java.lang.String.valueOf(
TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis()
)
) // Unix Timestamp
imageName = companyContact +"_$filets.png"
var instream: InputStream? = null
var outstream: OutputStream? = null
try {
val dir: File = File(root, imageName.replace(" ", "_"))
instream = FileInputStream(tmpFile.path)
outstream = FileOutputStream(dir.path)
val buffer = ByteArray(1024)
var read: Int
while (instream!!.read(buffer).also { read = it } != -1) {
outstream!!.write(buffer, 0, read)
}
instream.close()
instream = null
outstream!!.flush()
outstream.close()
outstream = null
} catch (fnfe1: FileNotFoundException) {
fnfe1.message?.let { it1 -> Log.e("FileNotFoundException", it1) }
} catch (e: java.lang.Exception) {
Log.e("Exception", e.message!!)
}
}
Now i have my picture as png on my SD-Card.
Here is a simple solution for Kotlin 1.7.X
// move Uri file from cache to app's external files
// - from: data/data/com.example.myapp/cache/videos/file.mp4
// - to: sdcard/Android/data/com.example.myapp/files/Movies
// Note: not selectable by the user.
fun moveUriFileToAppMovies(fromUri: Uri, context: Context) {
val inputStream = context.contentResolver.openInputStream(fromUri)!!
val file = File(context.getExternalFilesDir(Environment.DIRECTORY_MOVIES),
fromUri.lastPathSegment ?: "video.mp4")
val outputStream = FileOutputStream(file)
inputStream.copyTo(outputStream)
inputStream.close()
outputStream.close()
}