I am using the following to store images created in my app in the gallery of Android:
MediaStore.Images.Media.insertImage(contentResolver, bitmap, "SomeTitle", "Description");
This will store the images in the Picture-Device-Folder and add them to the Gallery.
I now want to create a specific image folder for my app, so that images are stored in the folder "MyApp" instead of "Picture". How can I do that?
I found the solution hidden here: https://stackoverflow.com/a/57265702/289782
I will quote it here since the original question is rather old and the great answer by User Bao Lei is ranked rather low.
There were several different ways to do it before API 29 (Android Q) but all of them involved one or a few APIs that are deprecated with Q. In 2019, here's a way to do it that is both backward and forward compatible:
(And since it is 2019 so I will write in Kotlin)
/// #param folderName can be your app's name
private fun saveImage(bitmap: Bitmap, context: Context, folderName: String) {
if (android.os.Build.VERSION.SDK_INT >= 29) {
val values = contentValues()
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + folderName)
values.put(MediaStore.Images.Media.IS_PENDING, true)
// RELATIVE_PATH and IS_PENDING are introduced in API 29.
val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
if (uri != null) {
saveImageToStream(bitmap, context.contentResolver.openOutputStream(uri))
values.put(MediaStore.Images.Media.IS_PENDING, false)
context.contentResolver.update(uri, values, null, null)
}
} else {
val directory = File(Environment.getExternalStorageDirectory().toString() + separator + folderName)
// getExternalStorageDirectory is deprecated in API 29
if (!directory.exists()) {
directory.mkdirs()
}
val fileName = System.currentTimeMillis().toString() + ".png"
val file = File(directory, fileName)
saveImageToStream(bitmap, FileOutputStream(file))
if (file.absolutePath != null) {
val values = contentValues()
values.put(MediaStore.Images.Media.DATA, file.absolutePath)
// .DATA is deprecated in API 29
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
}
}
private fun contentValues() : ContentValues {
val values = ContentValues()
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png")
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
return values
}
private fun saveImageToStream(bitmap: Bitmap, outputStream: OutputStream?) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
outputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
EDIT: Not needed in Android 11+: (Also, before calling this, you need to have WRITE_EXTERNAL_STORAGE first.)
Related
I can not read a file that I'm saving. I have declared and asking for storage permissions.
fun saveImage(bitmap: Bitmap, context: Context): String {
if (android.os.Build.VERSION.SDK_INT >= 29) {
val values = contentValues()
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + context.getString(R.string.app_name))
values.put(MediaStore.Images.Media.IS_PENDING, true)
// RELATIVE_PATH and IS_PENDING are introduced in API 29.
val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values).also { uri ->
if (uri != null) {
if (uri != null) {
saveImageToStream(bitmap, context.contentResolver.openOutputStream(uri))
values.put(MediaStore.Images.Media.IS_PENDING, false)
context.contentResolver.update(uri, values, null, null)
}
//val split = file.path.split(":".toRegex()).toTypedArray() //split the path.
Log.d(
"Tag",
"v--- uri.path - ${uri.path}, "
)
return uri.path.toString() //assign it to a string(your choice).
}
}
} else { // below API 29 }
}
then in onCreate or any other method I want to access that file and show on preview.
val previousPath = """/external/images/media/356""" //preferenceManager.getString(...)
//val file = File(path)
//previewImage.setImageURI(Uri.parse(previousPath))
val file = FileUtils().getFile(applicationContext, Uri.parse(previousPath))
Log.d("Tag", "Yo --- "+file?.path + " , "+ file?.name)
// val myBitmap = BitmapFactory.decodeFile()
// previewImage.setImageBitmap()
But this is always empty. With this getFile method, I get null pointer exception. I need the file to upload to server. How can I get this file?
val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
You used that obtained uri to save your file content to storage with:
context.contentResolver.openOutputStream(uri)
Now if you wanna read that file use the same uri again. Just use:
context.contentResolver.openInputStream(uri)
Use the same uri!
Dont mess around with the File class.
In Android 10 or higher, I used MediaStore to save files in Downloads, shared storage.
The code I used to save the file is as follows:
GlobalScope.launch {
val values = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, "file.txt")
put(MediaStore.Downloads.MIME_TYPE, "text/plain")
put(MediaStore.Downloads.IS_PENDING, 1)
put(MediaStore.Downloads.RELATIVE_PATH,Environment.DIRECTORY_DOWNLOADS+ File.separator+"MyApp/SubDir")
}
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val item = contentResolver.insert(collection, values)!!
contentResolver.openFileDescriptor(item, "w", null).use {
FileOutputStream(it!!.fileDescriptor).use { outputStream ->
outputStream.write("test file".toByteArray())
outputStream.close()
}
}
values.clear()
values.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(item, values, null, null)
Now my file is save in Downloads/myApp/subDir/file.txt
If so, how can I read this file if I don't know the Uri of this file but know the RELATIVE_PATH used to save it?
It is now possible with the helps of SimpleStorage:
val fileList = MediaStoreCompat.fromRelativePath(this, Environment.DIRECTORY_DOWNLOADS)
val singleFile = MediaStoreCompat.fromRelativePath(this, Environment.DIRECTORY_DOWNLOADS + "/MyApp/SubDir/", "file.txt")
I have a fun that saves bitmap as PNG or JPG (both not working), but seems like using content values not working as expected.
File name is incorrect.
File type is incorrect.
What am I missing ?
Works on Android 10, but not working on Android 8
fun Bitmap.save(context: Context) {
val contentResolver = context.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "test.png")
put(MediaStore.MediaColumns.TITLE, "test")
put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
put(MediaStore.MediaColumns.IS_PENDING, 1)
}
}
val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val uri = contentResolver.insert(contentUri, contentValues)
if (uri != null) {
try {
contentResolver.openFileDescriptor(uri, "w", null)?.use {
if (it.fileDescriptor != null) {
with(FileOutputStream(it.fileDescriptor)) {
compress(
Bitmap.CompressFormat.PNG,
DEFAULT_IMAGE_QUALITY,
this
)
flush()
close()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.clear()
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
contentResolver.update(uri, contentValues, null, null)
}
MediaScannerConnection.scanFile(context, arrayOf(uri.toString()), null, null)
}
recycle()
}
Actual file name is 1592205828045 (some timestamp)
Actual file type is jpg with 0B - as it was not saved properly ?
You will have to maintain 2 different ways of saving images to shared storage. This post covers it quite well. Using Media Store API in older phones results in the problem you have described. Some code sample for you (tested in Android 8, 10, and 11).
Add these to your manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- File save functions handles this -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
Add a permission check to your app (code not provided)
When you are ready with your bitmap call either of these functions (depending on the SDK version of the phone that the app is currently running on)
//TODO - bitmap needs null check
val bitmap = BitmapFactory.decodeFile(bitmapFile.canonicalPath)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
{
saveBitmapPreQ(bitmap)
} else {
saveBitmapPostQ(bitmap)
}
Finally these are the implementations of saveBitmapPreQ and saveBitmapPostQ
#Suppress("DEPRECATION") // Check is preformed on function call
private fun saveBitmapPreQ(thisBitmap: Bitmap){
Log.d("HOME_4", "in pre Q")
val pictureDirectory =
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
"MyFolder")
if (!pictureDirectory.exists()){
pictureDirectory.mkdir()
}
val dateTimeStamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
val name = "Image_$dateTimeStamp"
val bitmapFile = File(pictureDirectory, "$name.png")
try {
val fileOutputStream = bitmapFile.outputStream()
thisBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
fileOutputStream.flush()
fileOutputStream.close()
} catch (e: Exception) {
Log.d("HOME_5", "Pre Q error $e")
}
}
private fun saveBitmapPostQ(thisBitmap: Bitmap){
Log.d("HOME_6", "in post Q")
val dateTimeStamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
val name = "Image_$dateTimeStamp"
val relativePath = Environment.DIRECTORY_PICTURES + File.separator + "MyFolder"
val contentValues = ContentValues().apply {
put(MediaStore.Images.ImageColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
put(MediaStore.MediaColumns.TITLE, name)
put(MediaStore.Images.ImageColumns.RELATIVE_PATH, relativePath)
}
val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
var outputStream: OutputStream? = null
var uri: Uri? = null
try {
uri = contentResolver.insert(contentUri, contentValues)
if (uri == null){
throw IOException("Failed to create new MediaStore record.")
}
outputStream = contentResolver.openOutputStream(uri)
if (outputStream == null){
throw IOException("Failed to get output stream.")
}
if (!thisBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)){
throw IOException("Failed to save bitmap.")
}
} catch (e: IOException){
if (uri != null)
{
contentResolver.delete(uri, null, null)
}
throw IOException(e)
}
finally {
outputStream?.close()
}
}
I have left log messages in there to help you understand the flow. In the saveBitmapPostQ funtions I have taken a few shortcuts. Please read this post under the headding Creating a New File on how you can improve that function further.
You are creating the file, but you still need to write your Bitmap to it:
fun Bitmap.save(context: Context) {
...
val bitmap = this
val maxImageQuality = 100
val uri = contentResolver.insert(contentUri, contentValues)
if (uri != null) {
try {
contentResolver.openFileDescriptor(uri, "w", null)?.use {
if (it.fileDescriptor != null) {
with(FileOutputStream(it.fileDescriptor)) {
bitmap.compress(
Bitmap.CompressFormat.PNG,
maxImageQuality, this
)
flush()
close()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
// release pending status of the file
contentValues.clear()
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(uri, contentValues, null, null)
// notify media scanner there's a new picture
MediaScannerConnection.scanFile(context, arrayOf(uri.toString()), null, null)
}
// don't forget to recycle the bitmap when you don't need it any longer
bitmap.recycle()
}
I have searched high and low and not found an answer to my particular question, I hope someone can help.
I am developing this for Android 9 and above, the code I use for older releases works fine.
It's quite simple, I have stored an image in the MediaStore, I have found the image in the media store, I return its path, I check the path exists and it does, it also has a correct size and is visible in the android Gallery. So why when I try to open it with
val bitmap2 = BitmapFactory.decodeFile(fullPath)
bitmap2 comes back as null - no errors are generated by the above command. The parseAllImages function was taken from the web and tweaked slightly but seems to work ok as far as I can tell.
sample code
private fun setPic() {
if (mediaPath.isNotEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val content = GalleryAdd.parseAllImages(requireActivity(), mediaPath)
val fullPath = content
if (File(fullPath).exists()) {
val tester = File(fullPath).length()
val bitmap2 = BitmapFactory.decodeFile(fullPath)
viewModel.setBitmap(bitmap2)
}
}
}
}
fun parseAllImages(act : Activity, name : String) : String {
try {
val projection =
arrayOf(MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID)
val cursor = act.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, // Which columns to return
null, // Return all rows
null,
null
)
val size: Int = cursor!!.getCount()
/******* If size is 0, there are no images on the SD Card. */
if (size == 0) {
} else {
val thumbID = 0
if (cursor != null) {
while (cursor.moveToNext()) {
val file_ColumnIndex: Int =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
/**************** Captured image details */
/***** Used to show image on view in LoadImagesFromSDCard class */
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val path: String = cursor.getString(file_ColumnIndex)
val fileName =
path.substring(path.lastIndexOf("/") + 1, path.length)
if (fileName == name)
{
return path
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
Code snippet I use to write to the media store
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver: ContentResolver = activity.contentResolver
val contentValues = ContentValues()
contentValues.put(
MediaStore.MediaColumns.DISPLAY_NAME,
fileName
)
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
contentValues.put(
MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES + File.separator + "fishy"
)
val imageUri: Uri? =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
val tester = imageUri.toString() + File.separator + Environment.DIRECTORY_PICTURES + File.separator + "fishy" + File.separator + fileName
scanLoc = fileName
fos = resolver.openOutputStream(imageUri!!)!!
val file = File(currentPhotoPath)
val ins: InputStream = file.inputStream()
ins.copyTo(fos)
}
Any help or can someone point me to sample code that can read a jpg image from the mediastore given it's name? It's not production ready so please forgive lack of error checks.
Thanks
Lee.
In Android Studio I want to save a BitMap to a specific folder in the galery of the android device for example /test_pictures as an image.
The easy ways I found on the internet seem to be all deprecated, so it is not good practice to use those.
Does anyone have an easy example code on how to achieve this in Kotlin?
Kotlin bitmap extension like this:
fun Bitmap.saveImage(context: Context): Uri? {
if (android.os.Build.VERSION.SDK_INT >= 29) {
val values = ContentValues()
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/test_pictures")
values.put(MediaStore.Images.Media.IS_PENDING, true)
values.put(MediaStore.Images.Media.DISPLAY_NAME, "img_${SystemClock.uptimeMillis()}")
val uri: Uri? =
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
if (uri != null) {
saveImageToStream(this, context.contentResolver.openOutputStream(uri))
values.put(MediaStore.Images.Media.IS_PENDING, false)
context.contentResolver.update(uri, values, null, null)
return uri
}
} else {
val directory =
File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString() + separator + "test_pictures")
if (!directory.exists()) {
directory.mkdirs()
}
val fileName = "img_${SystemClock.uptimeMillis()}"+ ".jpeg"
val file = File(directory, fileName)
saveImageToStream(this, FileOutputStream(file))
if (file.absolutePath != null) {
val values = contentValues()
values.put(MediaStore.Images.Media.DATA, file.absolutePath)
// .DATA is deprecated in API 29
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
return Uri.fromFile(file)
}
}
return null
}
fun saveImageToStream(bitmap: Bitmap, outputStream: OutputStream?) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
outputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
val imageUri = YourBitmap.saveImage(applicationContext)