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.
Related
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()
}
I am working with Android version 10.
I have enabled Permissions to Read & Write Storage
Device Name : Poco F1
Scenario: I have to capture a screenshot of the current layout and save it to internalStorage and preview that image to the user. Here users have an option to delete the image.
Here are the codes I am using to save & delete
Saving a screenshot:
//I will pass the bitmap here
fun saveBitmapToInternalStorage(bitmap: Bitmap?) {
bitmap?.let {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
saveBitmapToOlderDevice(it)
} else {
saveBitmapToNewerDevice(it)
}
}
}
//This method is to save image to newerdevice >= Q
#RequiresApi(Build.VERSION_CODES.Q)
private fun saveBitmapToNewerDevice(bitmap: Bitmap) {
val uri = generateUri()
context.contentResolver.openOutputStream(uri ?: return).use { outputStream ->
outputStream?.let {
writeBitmapToJpeg(bitmap, outputStream, uri.toString())
}
}
}
//This is to generate the URI.
#RequiresApi(Build.VERSION_CODES.Q)
private fun generateUri(): Uri? {
val dateFormat = getDateFormat()
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "${dateFormat}.jpg")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/${context.resources.getString(R.string.app_name)}")
}
return context.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
}
// To save images to olderDevice
private fun saveBitmapToOlderDevice(bmp: Bitmap) {
val filename = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)?.absolutePath +
"/${context.resources.getString(R.string.app_name)}/${getDateFormat()}.jpg"
createDirectory(filename)
val outputStream = FileOutputStream(filename)
writeBitmapToJpeg(bmp, outputStream, filename)
}
//This method is to save the image to InternalStorage
private fun writeBitmapToJpeg(bmp: Bitmap, outputStream: OutputStream, imagePath: String) {
try {
val outputData = ByteArrayOutputStream()
bmp.compress(CompressFormat.JPEG, 100, outputData)
outputData.writeTo(outputStream)
outputStream.flush()
outputStream.close()
} catch (e: IOException) {
showBitmapWriteErrorMessage()
}
}
I save the path while storing the image in internalStorgae
the path looks like
/storage/emulated/0/Pictures/TGP AR/20211011142001.jpg
and i pass this path into below method
To delete the image :
private fun deleteImage(imagePath: String) {
val file = File(imagePath)
file.delete()
}
file.exists() is returning true.
file.delete() is returning false.
I think, there might be two different ways to delete ( > & < Q ).
Please help me
You can delete the image by modifying your method to the following:
private fun deleteImage(imagePath: Uri) {
getContentResolver().delete(imagePath, null, null)
}
Then pass the Uri created in generateUri() to delete the file.
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
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'm using Storage Access Network to pick file and save in internal storage so that app can use if in future.
I'm getting URI without any issues. It's something like content://com.android.providers.media.documents/document/image%3A141274
Problem comes when I'm trying to save image into internal directory. Code passes without crashes, image with same size is saved into internal directory (I can see it in device Explorer: https://take.ms/3TwBS).
But image itself is broken and can't be opened.
Here's code I'm using (after getting URI)
val destinationFile = File("${context.filesDir.absolutePath}/$fileName")
try {
val writer = FileWriter(destinationFile)
writer.append(readTextFromUri(it))
writer.flush()
writer.close()
} catch (e: Exception) {
e.printStackTrace()
}
#Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
val inputStream = activity!!.contentResolver.openInputStream(uri)
val reader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
var line: String? = null
while ({ line = reader.readLine(); line }() != null) {
stringBuilder.append(line)
}
inputStream?.close()
reader.close()
return stringBuilder.toString()
}
As #CommonsWare described I should have used proper dealing with files, not texts.
Proper way to do:
private fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
val output = FileOutputStream(File("${filesDir.absoluteFile}/magic.png"))
inputStream?.copyTo(output, 4 * 1024)
}
Or longer way (without extension functions)
fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
inputStream.use {
val directory = getDir("test", Context.MODE_PRIVATE)
val file = File(directory, "correct.txt")
val output = FileOutputStream(file)
output.use {
val buffer = ByteArray(4 * 1024) // or other buffer size
var read: Int = inputStream?.read(buffer) ?: -1
while (read != -1) {
output.write(buffer, 0, read)
read = inputStream?.read(buffer) ?: -1
}
output.flush()
}
}
}