Home > Mobile >  Using nested CoroutineScopes to upload images and keeping track of them
Using nested CoroutineScopes to upload images and keeping track of them

Time:11-19

I am a newbie to android coroutines my requirements

  1. Need to upload 20 images
  2. Keep track of upload(at least when it gets finished I need to hide progressBar of each image)
  3. After uploading all the images need to enable a "next" button also Here is my try:

private fun startUploading(){
    // Get AWS data
    val accessKey = sharedPreferences.getString(getString(R.string.aws_access_key), "").toString()
    val secretKey = sharedPreferences.getString(getString(R.string.aws_secret_key), "").toString()
    val bucketName = sharedPreferences.getString(getString(R.string.aws_bucket_name), "").toString()
    val region = sharedPreferences.getString(getString(R.string.aws_region), "").toString()
    val distributionUrl = sharedPreferences.getString(getString(R.string.aws_distribution_url), "").toString()

    var totalImagesNeedToUpload = 0
    var totalImagesUploaded = 0
    CoroutineScope(Dispatchers.IO).launch {
        for (i in allCapturedImages.indices) {
            val allImageFiles = allCapturedImages[i].viewItem.ImageFiles
            totalImagesNeedToUpload  = allImageFiles.size
            for (j in allImageFiles.indices) {
                CoroutineScope(Dispatchers.IO).launch {
                    while (true) {
                        val internetActive = utilsClassInstance.hasInternetConnected()
                        if (internetActive){
                            try {
                                val file = allImageFiles[j]
                                if (!file.uploaded) {
                                    // Upload the file
                                    val cfUrl = utilsClassInstance.uploadFile(file.imageFile, accessKey, secretKey, bucketName, region, distributionUrl)

                                    // Set the uploaded status to true
                                    file.uploaded = true
                                    file.uploadedUrl = cfUrl

                                    // Increment the count of total uploaded images
                                    totalImagesUploaded  = 1

                                    // Upload is done for that particular set image
                                    CoroutineScope(Dispatchers.Main).launch {
                                        mainRecyclerAdapter?.uploadCompleteForViewItemImage(i, j, cfUrl)
                                        // Set the next button enabled
                                        if (totalImagesUploaded == totalImagesNeedToUpload){
                                            binding.btnNext.isEnabled = true
                                        }
                                    }
                                    break
                                }else{
                                    totalImagesUploaded  = 1
                                    break
                                }
                            } catch (e: Exception) {
                                println(e.printStackTrace())
                            }
                        }
                    }
                    CoroutineScope(Dispatchers.Main).launch {
                        if (totalImagesUploaded == totalImagesNeedToUpload){
                            updateProgressForAllImages()
                            binding.btnNext.isEnabled = true
                        }
                    }
                }
            }
        }
    }
}

    fun uploadFile(file: File, accessKey:String, secretKey:String, bucketName: String, region:String, distributionUrl: String): String{
    // Create a S3 client
    val s3Client = AmazonS3Client(BasicAWSCredentials(accessKey, secretKey))
    s3Client.setRegion(Region.getRegion(region))

    // Create a put object
    val por = PutObjectRequest(bucketName, file.name, file)
    s3Client.putObject(por)

    // Override the response headers
    val override = ResponseHeaderOverrides()
    override.contentType = "image/jpeg"

    // Generate the url request
    val urlRequest = GeneratePresignedUrlRequest(bucketName, file.name)
    urlRequest.responseHeaders = override

    // Get the generated url
    val url = s3Client.generatePresignedUrl(urlRequest)
    return url.toString().replace("https://${bucketName}.s3.amazonaws.com/", distributionUrl)
}        
  1. There are total "n" images that I need to upload
  2. every image is getting uploaded in different Coroutine because I need to do the parallel upload

The whole question is how to know that all the images are uploaded and enable a next button?

CodePudding user response:

Your code seems very unstructured. You have an infinite loop checking for network availability. You have a nested loop here to upload images (Why?). You are creating a lot of coroutine scopes and have no control over them

Based on the 3 requirements that you mentioned in the question, you can do something like this:

val imagesToUpload: List<File> = /* ... */
var filesUploaded = 0
lifecycleScope.launchWhenStarted { 
    coroutineScope { // This will return only when all child coroutines have finished
        imagesToUpload.forEach { imageFile ->
            launch { // Run every upload in parallel
                val url = utilsClassInstance.uploadFile(file.imageFile, ...) // Assuming this is a non-blocking suspend function.
                filesUploaded  
                // Pass the `url` to your adapter to display the image
                binding.progressBar.progress = (filesUploaded * 100) / imagesToUpload.size // Update progress bar
            }
        }
    }
    // All images have been uploaded at this point.
    binding.btnNext.enabled = true
}

Ideally you should have used a viewModelScope and the upload code should be in a repository, but since you don't seem to have a proper architecture in place, I have used lifecycleScope which you can get inside an Activity or Fragment

  • Related