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")
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.
I am saving a file inside the Downloads directory of the device (Android 11) to be viewed later by my app. I'm allowing multiple file types like pdf, word etc. I was able to save the file like this: (I got this code sample from here)
#TargetApi(29)
private suspend fun downloadQ(
url: String,
filename: String,
mimeType: String
) =
withContext(Dispatchers.IO) {
val response = ok.newCall(Request.Builder().url(url).build()).execute()
if (response.isSuccessful) {
val values = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, filename)
put(MediaStore.Downloads.MIME_TYPE, mimeType)
put(MediaStore.Downloads.IS_PENDING, 1)
}
val resolver = context.contentResolver
val uri =
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
uri?.let {
resolver.openOutputStream(uri)?.use { outputStream ->
val sink = outputStream.sink().buffer()
response.body()?.source()?.let { sink.writeAll(it) }
sink.close()
}
values.clear()
values.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(uri, values, null, null)
} ?: throw RuntimeException("MediaStore failed for some reason")
} else {
throw RuntimeException("OkHttp failed for some reason")
}
}
But when I tried to retrieve the file, I tried with the following ways that did not work:
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Downloads._ID)
val id = cursor.getLong(idColumn)
Log.d("uri id ","$id")
val contentUri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI,id)
This approach threw an exception:
java.lang.IllegalArgumentException: Failed to find configured root that contains /external/downloads/78
I got this ID (here 78) from the query and cursor from ContentResolver.query() and I hoped it to return the Uri from which I could get the File.
The second approach was this:
val uri = MediaStore.Downloads.getContentUri("external",id)
uri.path?.let { filePath ->
Log.d("uri path ",filePath)
val file = File(filePath)
} ?: Log.d("uri path ","null")
I used external as the directory based on this answer, but this approach also threw the same exception
java.lang.IllegalArgumentException: Failed to find configured root that contains /external/downloads/78
At the end what ended up working was hardcoding something like this after I used a file explorer app to view the exact directory path:
val file = File("storage/emulated/0/Download/$name.$extension")
So my question is, how do I get the value of this path dynamically, and is this path the same for all devices that can be used like this way?
EDIT: I also wanted to know if I am using the filename and it's extension to view the file, then if user downloads another file with same name then how do I make sure that correct file is opened? (even if i make a separate directory for my app inside Download, user could still download the same file twice that has a name like storage/emulated/0/Download/myDir/file(2).extension )
Try with the following code it will help you.
private fun readFile(){
val FILENAME = "user_details.txt"
val dir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.toString() + "/" + "folderName"
)
} else {
File(
Environment.getExternalStorageDirectory()
.toString() + "/${Environment.DIRECTORY_DOWNLOADS}/" + "folderName"
)
}
dir.apply {
if(this.exists()) File(this, FILENAME).apply {
FileInputStream(this).apply {
val stringBuffer = StringBuffer()
var i: Int
while (this.read().also { i = it } != -1) {
stringBuffer.append(i.toChar())
}
close()
}
}
}
}
You can use
{Environment.DIRECTORY_DOWNLOADS} + "/folderName/file_name.mime_type"
/storage/emulated/0/Download/12d82c65-00a5-4c0a-85bc-238c28005c33.bin
So, I need to save image to the Picture folder. There will be some subfolders too. But every time I want to save an image it is giving this error:
Failed to create new mediaStore record
This is what I tried:
#RequiresApi(Build.VERSION_CODES.Q)
#Throws(IOException::class)
fun saveMediaToStorage(context: Context, bitmap: Bitmap, format: CompressFormat, fName: String, folderName: String, extension: String) {
//Generating a file name
val filename = if (fName.contains("png")) fName.replace(".png", "") else fName.replace(".jpg", "")
//Output stream
var fos: OutputStream? = null
val relativePath = Environment.DIRECTORY_PICTURES + File.separator + folderName
Log.d(TAG, "saveMediaToStorage: Relative Path: $relativePath")
//getting the contentResolver
context.contentResolver?.also { resolver ->
//Content resolver will process the contentvalues
val contentValues = ContentValues().apply {
//putting file information in content values
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/$extension")
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
}
//Inserting the contentValues to contentResolver and getting the Uri
val imageUri= resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) ?:
throw IOException("Failed to create new mediaStore record") // This line is triggering every time
//Opening an outputstream with the Uri that we got
fos = imageUri.let { resolver.openOutputStream(it) }
}
fos?.use {
//Finally writing the bitmap to the output stream that we opened
bitmap.compress(format, 100, it)
}
}
What I can understand is, the imageUri is coming null. But why so, afaik I am doing ok..
What can be the reason, where I am doing wrong.. Any help would be highly appreciated.
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.)
When a user takes a photo in my app the image gets saved locally in the internal storage. I also allow then to be moved to the external storage where they can grab then by plugging the device into a computer and getting them off of there if they want.
I would do that by doing this
val tempDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "My_App_Folder")
val baseFile = File(filePath)
if(baseFile.exists()){
val targetFile = File(directory.path+File.separator+targetFileName)
baseFile.copyTo(targetFile)
baseFile.delete()
}
Image would end up in Pictures/My_App_Folder like I want
Now trying to move to MediaStore to support the scoped storage I am not sure how to save the file to the same folder.
I tried doing
val values:ContentValues = ContentValues()
values.put(MediaStore.Images.Media.MIME_TYPE,"image/jpg")
values.put(MediaStore.Images.Media.DATE_ADDED,System.currentTimeMillis() / 1000)
values.put(MediaStore.Images.Media.TITLE, targetFileName)
values.put(MediaStore.Images.Media.DISPLAY_NAME, targetFileName)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
values.put(MediaStore.Images.Media.DATE_TAKEN,System.currentTimeMillis())
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES+"/My_App_Folder")
uri = contentResolver.insert(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL),values)
}else{
uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)
}
But nothing appears in that folder now, I feel like I am missing something fundamental with MediaStore and I am not sure what it is
As Mike pointed out I have to save the image using the output stream from the uri after inserting into the content resolver so a working backwards compatible example looks like this.
private fun saveFileToExternalStorage(directory:File?,filePath:String,targetFileName:String){
var uri:Uri? = null
val values:ContentValues = ContentValues()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
values.put(MediaStore.Images.Media.MIME_TYPE,"image/jpg")
values.put(MediaStore.Images.Media.DATE_ADDED,System.currentTimeMillis() / 1000)
values.put(MediaStore.Images.Media.TITLE, targetFileName)
values.put(MediaStore.Images.Media.DISPLAY_NAME, targetFileName)
values.put(MediaStore.Images.Media.DATE_TAKEN,System.currentTimeMillis())
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES+"/My_App_Folder")
values.put(MediaStore.Images.Media.IS_PENDING, 1)
uri= contentResolver.insert(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),values)
}
val baseFile = File(filePath)
if(baseFile.exists()){
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){
directory?.let {
val targetFile = File(it.path+File.separator+targetFileName)
baseFile.copyTo(targetFile)
}
}else{
uri?.let {
val outputStream: OutputStream? = contentResolver.openOutputStream(uri)
outputStream?.let{
val inputStream: InputStream = File(filePath).inputStream()
inputStream.copyTo(outputStream,1024)
}
values.clear()
values.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(uri,values,null,null)
}
}
baseFile.delete()
}
}