1

I have been trying to upload multiple images to Firebase Storage. But, I am not able to do it successfully. I could successfully upload the image (single) to the storage and add the URL of the image to the Firestore, now that I revised my code to upload up to five images, it could be any number of images from 1 to 5.

    R.id.btn_submit -> {
    if (validateDetails()) {
        uploadImage()
    }
}

The above code, calls the following function after validating the fields, which then calls the function uploadImageToCloudStorage. mSelectedImageFileUriList is private var mSelectedImageFileUriList: MutableList<Uri?>? = null. It all seems to work correctly.

    private fun uploadImage() {
    showProgressDialog(resources.getString(R.string.please_wait))
    FirestoreClass().uploadImageToCloudStorage(
        this@AddProductActivity,
        mSelectedImageFileUriList,
        Constants.PRODUCT_IMAGE,
        Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
        et_product_title.text.toString().trim { it <= ' ' }
    )
}

Following code is where I guess is a mistake.

    fun uploadImageToCloudStorage(
     activity: AddProductActivity,
     imageFileURI: MutableList<Uri?>?,
     imageType: String,
     directoryName: String,
     title: String
 ) {

     var i = 0
     val imageURLList = ArrayList<String>()
     val itr = imageFileURI?.iterator()

     if (itr != null) {

         while (itr.hasNext()) {

             val sRef: StorageReference = FirebaseStorage.getInstance().getReference(
                 "/$directoryName/" + imageType + "." + Constants.getFileExtension(
                     activity,
                     imageFileURI[i]
                 )
             )
            
             sRef.putFile(imageFileURI[i]!!)
                 .addOnSuccessListener { taskSnapshot ->  
                     taskSnapshot.metadata!!.reference!!.downloadUrl
                         .addOnSuccessListener { uri ->
                             
                             if (i < imageFileURI.size) {
                                 i += 1
                                 imageURLList.add(uri.toString())

                             } else {
                                 activity.imageUploadSuccess(imageURLList)
                             }
                         }
                 }
                 .addOnFailureListener { exception ->
                     activity.hideProgressDialog()
                   Log.e(
                         activity.javaClass.simpleName,
                         exception.message,
                         exception
                     )
                 }

         }
     } else {
         Toast.makeText(
             activity,
             "There is no images in the ArrayList of URI",
             Toast.LENGTH_SHORT
         ).show()
     }
 }

EDIT: After receiving the first answer.

I have created a QueueSyn.kt file and added the code in the Answer. The activity where the images and the button are changed to

class AddProductActivity : BaseActivity(), View.OnClickListener, QueueSyncCallback {

The following function is called when the button is hit.

    private fun uploadProductImage() {

    showProgressDialog(resources.getString(R.string.please_wait))

   QueueSync(
        mSelectedImageFileUriList,
        Constants.PRODUCT_IMAGE,
        Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
        et_product_title.text.toString().trim { it <= ' ' },
        this
    ).startUploading()

}

I have also implemented these two methods in the class AddProductActivity, but I don't know what should go inside this.

    override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
    TODO("Not yet implemented")
}

override fun getFileExtension(uri: Uri): String {
    TODO("Not yet implemented")
}

Error:

enter image description here

enter image description here

Codist
  • 737
  • 8
  • 23
  • Is there any error message? If there is please edit. – Rahul Rawat Jul 12 '21 at 06:39
  • 1
    Maybe try sending files one by one – Rahul Rawat Jul 12 '21 at 15:26
  • I am sending one by one with `sRef.putFile(imageFileURI[i]!!)` where 'i' is the index of the `uri` in the `MutableList` called 'imageFileURI'. Do you mean something else? I do not know if the way I iterate is wrong, I am not a pro. – Codist Jul 12 '21 at 18:34
  • 1
    What you are doing here is your while loop is iterating over all the files and inside it firebase storage is sending files async which means before a file is sent fully another one starts uploading. This way is not going to sustain in case you have a long list of files. What you should instead try is maybe use a recursive solution to synchronise file upload so only one is uploaded at a time or you can use a custom queue which uploads five or more files at once and then tries to upload the rest – Rahul Rawat Jul 13 '21 at 02:39
  • OK, I got you. But I don't know how to do that. Could you help me modify my code? – Codist Jul 13 '21 at 05:18
  • 1
    Sure let me cook something up. – Rahul Rawat Jul 13 '21 at 05:36
  • 1
    Try the edited answer – Rahul Rawat Jul 13 '21 at 11:53
  • Error is updated in the question, please check. – Codist Jul 13 '21 at 13:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234819/discussion-between-rahul-rawat-and-codist). – Rahul Rawat Jul 13 '21 at 13:11

1 Answers1

1

This should work

import android.net.Uri
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import java.util.*
import kotlin.collections.ArrayList

interface QueueSyncCallback {
    fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>)
    fun getFileExtension(uri: Uri): String
}

class QueueSync(
    imageFileURI: MutableList<Uri?>?,
    private val imageType: String,
    private val directoryName: String,
    private val title: String,
    private val callback: QueueSyncCallback,
    private val maxActive: Int = 5
) {
    private val queue: LinkedList<Uri> = LinkedList()
    private val runningQueue: MutableList<Uri> = Collections.synchronizedList(
        object : ArrayList<Uri>() {
            override fun remove(element: Uri): Boolean {
                val removed = super.remove(element)

                if (isEmpty() && queue.isEmpty()) {
                    callback.completed(successList, failureList)
                } else if (queue.isNotEmpty()) {
                    addToRunningQueue()
                }
                return removed
            }
        }
    )
    private val successList: MutableList<Uri> = Collections.synchronizedList(ArrayList())
    private val failureList: MutableList<Uri> = Collections.synchronizedList(ArrayList())

    init {
        if (imageFileURI != null)
            for (uri in imageFileURI) {
                if (uri != null)
                    queue.add(uri)
            }
    }

    private fun getLocation(uri: Uri) = "/$directoryName/$imageType.${callback.getFileExtension(uri)}"

    fun startUploading() {
        var i = 0

        if (queue.isEmpty()) {
            callback.completed(successList, failureList)
            return
        }

        while (i < maxActive && queue.isNotEmpty()) {
            addToRunningQueue()
            i++
        }
    }

    private fun addToRunningQueue() {
        val uri = queue.poll()!!
        runningQueue.add(uri)
        uploadImageToCloudStorage(uri)
    }

    private fun uploadImageToCloudStorage(locationUri: Uri) {
        val sRef: StorageReference = FirebaseStorage.getInstance().getReference(getLocation(locationUri))

        sRef.putFile(locationUri)
            .addOnSuccessListener { taskSnapshot ->
                taskSnapshot.metadata!!.reference!!.downloadUrl
                    .addOnSuccessListener { uri ->
                        successList.add(uri)
                        runningQueue.remove(locationUri)
                    }
            }
            .addOnFailureListener {
                failureList.add(locationUri)
                runningQueue.remove(locationUri)
            }
    }
}

Since your need requires usage of threads so to prevent race conditions I had to use Collections.synchronizedList. To use this you need to implement QueueSyncCallback in your activity and pass it as a reference to QueueSync. Make sure that any piece of code written inside completed is wrapped inside runOnMainThread if it is going to access views in any way since completed will not run on main thread as far as I know. This should work however I am not able to test it since it is based on your current code.

Edit:- Answering after edit

override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
    imageUploadSuccess(successList)
    hideProgressDialog()
}

override fun getFileExtension(uri: Uri): String {
    Constants.getFileExtension(this, imageFileURI[i])
}
Rahul Rawat
  • 360
  • 2
  • 9
  • If it works leave an upvote and accept this answer for others. – Rahul Rawat Jul 13 '21 at 06:45
  • Sure, I will. I do not know where to start now. Let me check how to use code. Can you give me a hint? I am not an expert in it. – Codist Jul 13 '21 at 09:32
  • Just add this as a kt file and make QueueSync object after that just call startUploading on it – Rahul Rawat Jul 13 '21 at 09:34
  • OK, I am trying. What value should be passed for the parameter `callback` when I call `startUploading`. This is how I am doing it. `QueueSync( mSelectedImageFileUriList, Constants.PRODUCT_IMAGE, Constants.PRODUCT_IMAGE_DIRECTORY_NAME, et_product_title.text.toString().trim { it <= ' ' }, ).startUploading()` Also, "property 'title` is never used". Is something missing? – Codist Jul 13 '21 at 09:53
  • you will have to implement `QueueSyncCallback` in your activity and send `this` as callback – Rahul Rawat Jul 13 '21 at 10:08
  • I read that in your answer and been trying for that. – Codist Jul 13 '21 at 10:13
  • Maybe read [this](https://stackoverflow.com/questions/44132574/how-to-implement-this-java-interface-in-kotlin) – Rahul Rawat Jul 13 '21 at 10:19
  • I went through the link you have given and a couple of other tutorials and I am learning now. Can you look at my question after 'Edit'. Thank you so much for your time and effort. – Codist Jul 13 '21 at 11:45
  • Your code worked like a charm. Needed not a single edit or correction. It was awesome. I took more time because many things in your code were new to me. You have been so helpful. Many thanks. – Codist Jul 14 '21 at 08:22
  • Can you take a look at my new question? Thanks https://stackoverflow.com/q/68450099/13935956 – Codist Jul 20 '21 at 05:56