2

Trying to make a file downloadManager

we have a activity which contains recycler view, each item in recycler view represents a file which is either in downloading, downladed state. Each recycler view item has a callback this callback has 3 information: onSuccess (file), onError(Error), progress(Int). Now I want to extract this information in BindViewHolder to show the progress, but i cannot get value in callback: (removing other code for simplicity)

  class DownloadActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
      
        val list = mutableListOf<DItem>()
        list.add(DItem("url", callback = object : Listener{
            override fun onSuccess(file: File) {

            }

            override fun onFailure(error: Error) {
            }

            override fun onProgress(progress: Int) {
            }
        })
      // trigger from here for simplicity otherwise each list item has a download button which will trigger this:
      FileDownloadManager().download("utl", list[0].callback)
    }

// adapter
    class DownloadAdapter : RecyclerView.Adapter<MyViewhOdler>() {

  
    val list = mutableListOf<DItem>()
   

    override fun onBindViewHolder(holder: MyViewhOdler, position: Int) {
// how to get value here:
        list.get(0).callback
    }

 
} 
          

class FileDownloadManager {

  
     override fun download(url: String, callback: Listener) {

       // on donwloading start, will trigger
       // callback.progress(<some value>)

    }

   

    interface Listener{
        fun onSuccess(file: File)
        fun onFailure(error: Error)
        fun onProgress(progress: Int)
    }

   
}
    
    // Ditem
     data class DItem (
   var url: String? = null,
    var callback: Listener,
       var fcallback: ((File, Error, Int)-> Unit)

)

    }
3
  • At the risk of stating the obvious, does the class DItem have a public field called callback? The mere existence of a constructor parameter with that name does not imply that it's an instance variable. Commented Feb 28, 2023 at 3:07
  • @SilvioMayolo yes its public, added it Commented Feb 28, 2023 at 3:13
  • Warm welcome to SO. Please try to use correct upper case letters, e.g. in the beginning of your title, sentences or the word "I". This would be gentle to your readers. Please read How to ask and Minimal Reproducible Example. Then update your question with code to show us what you have tried so far. Commented Mar 20, 2023 at 9:48

2 Answers 2

3
+25

I think your problem stems from the fact that you have a list of domain models with complex behaviors in your adapter where it should be really dumb view models that just represent the current state of each list item.

onBindViewHolder is only there to slap the current state of the item at the given position to the matching view at the same position. It should not be holding on to callbacks or trying to update itself.

You could try wrapping your domain model in an object that maintains the current state of progress and then notifies the owning adapter that it's time to refresh.

For example:

This is the item that will be in the adapter. It will wrap the data model and expose the current state for binding to the view.

class ListItem(val url : String) {
    val currentProgress = MutableLiveData<Int>(0)
    val dItem = DItem(url, object : callback {
        fun onProgress(int progress) {
            currentProgress.postValue(progress)
        }
        // ...
        // Store file or error from other functions as well
    }
 }

Then your activity creates a list of these objects instead of DItem, observes the state change, and notifies the adapter that that index needs to be refreshed when it changes:

class DownloadActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val list = mutableListOf<ListItem>()
        list.add(ListItem("url").apply {
            currentProgress.observe(this@DownloadActivity) {
                // Simple implementation just refreshes the whole list when one progress changes
                // Exercise for the reader to pass along the specific index to mark as change
                adapter.notifyDataChanged()
            }
        })

Then your ViewHolder only cares about delegating the button click event and setting the view to the current state:

override fun onBindViewHolder(holder: MyViewhOdler, position: Int) {
    val listItem = list.get(position)
    holder.buttonThatTriggersDownload.setOnClickListener {
        // Set click listener to trigger download
    }
    holder.viewThatShowsDownloadProgress.value = listItem.currentProgress.value
}
Sign up to request clarification or add additional context in comments.

1 Comment

so using livedata is like adding one more callback to the system. I think it will work.
1

If I understand it correctly, you want to get the "progress" value in "onBindViewHolder()" but you don't know how to do it.

One of the first things that I thought about, not modifying much of the code but may also be a dirty solution, is:

You actually define your object callback later, so the constructor will not accept "callback" anymore, instead it will be a var that you will initialize later.

class DItem(
    var url: String? = null,
    var fcallback: ((File, Error, Int) -> Unit)
) {
    lateinit var callback: FileDownloadManager.Listener
}

At this point, in "onBindViewHolder()" you can now define your callback and get the value in that place:

fun onBindViewHolder() {
    println("onBindViewHolder")
    list[0].callback = object : FileDownloadManager.Listener {
        override fun onSuccess(file: File) {
        }
        override fun onFailure(error: Error) {
        }
        override fun onProgress(progress: Int) {
            print("progress: $progress")
        }
    }
}

I hope I did understand the issue correctly and I really want to stress the fact that this is not necessarily a clean solution but one of the fastest to implement and with the least amount of code refactoring.

1 Comment

thanks but this will cause error, assume we have 10 items for which we start download on screen start, assume the screen can show only 3 items, so on bindviewholder of only 3 items will be called (assume user has opened the screen and kept it idle no scrolling), but remaining 7 will try to download but their callbacks are not initialised so it will cause crash in filemanager download method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.