0

I was building an android app, for which I had to download media from FirebaseStorage to local storage using MediaStore for AndroidQ and above. For that I wrote the code below referring to this and this.

@RequiresApi(Build.VERSION_CODES.Q)
suspend fun downloadMedia(model: Message): Uri {
    Log.d(TAG, "downloadMedia: Q")
    val fileName = "SK ${model.timeStamp}.jpg"
    val dirPath = "${Environment.DIRECTORY_PICTURES}/Skara"
    val mimeType = "image/jpg"
    val collectionUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)

    val values = ContentValues().apply {
        put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
        put(MediaStore.Video.Media.RELATIVE_PATH, dirPath)
        put(MediaStore.Video.Media.MIME_TYPE, mimeType)
        put(MediaStore.Video.Media.IS_PENDING, 1)
    }

    val uri = contentResolver.insert(collectionUri, values)!!
    try {
        Firebase.storage.getReferenceFromUrl(model.mediaUrl).getFile(uri).await()
    } catch (e: StorageException) {
        Log.d(TAG, "downloadMedia: StorageException")
        Log.d(TAG, "downloadMedia: $uri")
        Log.d(TAG, "downloadMedia: ${e.message}")
        Log.d(TAG, "downloadMedia: ${e.cause}")
    }
    values.apply {
        clear()
        put(MediaStore.Video.Media.IS_PENDING, 0)
    }
    contentResolver.update(uri, values, null, null)
    return uri
}

But this code logged the following error.

2020-12-08 15:18:38.602 8124-8201/com.skb.skara D/MessageActivity: downloadMedia: Q
2020-12-08 15:18:39.872 8124-8201/com.skb.skara D/MessageActivity: downloadMedia: StorageException
2020-12-08 15:18:39.876 8124-8201/com.skb.skara D/MessageActivity: downloadMedia: content://media/external/images/media/680
2020-12-08 15:18:39.879 8124-8201/com.skb.skara D/MessageActivity: downloadMedia: An unknown error occurred, please check the HTTP result code and inner exception for server response.
2020-12-08 15:18:39.881 8124-8201/com.skb.skara D/MessageActivity: downloadMedia: java.io.IOException: No such file or directory

I don't know what is the cause of error and how to resolve it. Please help me. I have READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions. It is also creating 'Skara' folder inside the 'Pictures' folder, but the folder is empty.

Sourav Kannantha B
  • 2,860
  • 1
  • 11
  • 35
  • `(model.mediaUrl).getFile(uri)....` Are you shure you can use the mediastore uri for getFile()? – blackapps Dec 08 '20 at 10:24
  • `But this code logged the following error.` ? You mean: that code threw a StorageException. – blackapps Dec 08 '20 at 10:27
  • @blackapps No... I am totally unsure about that. I tried to find alternatives for that, but couldn't. So as a last hope I used this. – Sourav Kannantha B Dec 08 '20 at 10:27
  • @blackapps yes, code threw StorageException – Sourav Kannantha B Dec 08 '20 at 10:28
  • `java.io.IOException: No such file or directory` Blame the programmer that full path is not mentioned. I alway fire such programmers ;-). – blackapps Dec 08 '20 at 10:30
  • Try without the mediastore. – blackapps Dec 08 '20 at 10:33
  • Are you shure its the uri? Why not the url? – blackapps Dec 08 '20 at 10:35
  • @blackapps To avoid being fired from employees like you, I am learing these now itself ;-). Still I am in my degrees, no job yet. Anyway, how to get full path. – Sourav Kannantha B Dec 08 '20 at 10:35
  • @blackapps MediaStore is the sole way to handle media files in AndroidQ and above as I know. I already posted two questions here regarding alternatives and got no solutions. – Sourav Kannantha B Dec 08 '20 at 10:36
  • You cannot get full path. And i did not mean you of course. I ment the programmers of Firebase.storage.getReferenceFromUrl. They made a bad e.cause message. They do not mention full path. – blackapps Dec 08 '20 at 10:37
  • `MediaStore is the sole way to handle media files in AndroidQ and above ` No. You can save your download data to getExternalFilesDir and to other places using classic file functions. – blackapps Dec 08 '20 at 10:39
  • @blackapps `getExternalFilesDir`: These files are internal to the applications, and not typically visible to the user as media. - from docs – Sourav Kannantha B Dec 08 '20 at 10:42
  • Yes i know. But just try. If you can then i will tell you what to do to fullfill all your wishes. – blackapps Dec 08 '20 at 10:44
  • When is a StorageException thrown? If something is wrong with firebase storage (url) or with local storage (your uri). I asked that before. – blackapps Dec 08 '20 at 10:47
  • @blackapps `getExternalFilesDir` works :). But if media wont be visible to user then it would not be of much use. – Sourav Kannantha B Dec 08 '20 at 10:53
  • @blackapps it is wrong with local storage uri and not with firebase url because, glide is still able to load image if I give that firebase url. – Sourav Kannantha B Dec 08 '20 at 10:54
  • @blackapps Also I think these downloaded files are invisible to mediastore. They are being downloaded every time I open the activity, instead of downloading first time and opening the same in subsequent runs. – Sourav Kannantha B Dec 08 '20 at 11:12
  • For Android 10 devices request legacy external storage in manifest and you can write to Pictures/Skara yourself. For Android 11 devices you dont have to do anything as you can just create that directory and write files to it. – blackapps Dec 08 '20 at 11:49
  • If your code is downloading files again then something is wrong with your code. But that is a different problem. – blackapps Dec 08 '20 at 11:52
  • @blackapps nope, the code for download is not wrong.. Actually its mentioned in the [docs](https://developer.android.com/reference/android/content/Context#getExternalFilesDir(java.lang.String)) that media saved here are not visible to mediastore by default, but can be made visible using mediascanner. – Sourav Kannantha B Dec 08 '20 at 13:44

1 Answers1

0

Finally I have found out some working code for this. (If anyone knows a better alternative, please answer).

@RequiresApi(Build.VERSION_CODES.Q)
suspend fun downloadMedia(model: Message): Uri {
    val fileName = "SK ${model.timeStamp}.jpg"
    val dirPath = "${Environment.DIRECTORY_PICTURES}/Skara"
    val mimeType = "image/jpg"
    val collectionUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)

    val values = ContentValues().apply {
        put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
        put(MediaStore.Video.Media.RELATIVE_PATH, dirPath)
        put(MediaStore.Video.Media.MIME_TYPE, mimeType)
        put(MediaStore.Video.Media.IS_PENDING, 1)  // This indicates that, the file is not available for other processes until you reset it back to 0.
    }

    val uri = contentResolver.insert(collectionUri, values)!!
        contentResolver.openOutputStream(uri)?.use { ops ->
            Firebase.storage.getReferenceFromUrl(model.mediaUrl).stream.await().stream.use { ips ->
                val buffer = ByteArray(1024)
                while (true) {
                    val bytes = ips.read(buffer)
                    if (bytes == -1)
                        break
                    ops.write(buffer, 0, bytes)
                }
                // The above seven lines can be written in a single line as below. (from 'val buffer' to '}')
                // ips.copyTo(ops) - Kotlin extension function
            }
        }

    values.apply {
        clear()
        put(MediaStore.Video.Media.IS_PENDING, 0)  // Resetting back to zero after the download completes.
    }
    contentResolver.update(uri, values, null, null)
    return uri
}

Still, I have queries with this answer, which I have asked here.

Sourav Kannantha B
  • 2,860
  • 1
  • 11
  • 35