1

I have a recyclerview that uses Glide to display images from Firebase Realtime database. In the recyclerview I also have a download button. When a user clicks the button, want the image from Firebase to be downloaded into the device internal storage.

Adapter class

    class NatureAdapter(private val mContext: Context, private val natureList: ArrayList<Nature>) : RecyclerView.Adapter<NatureAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.nature_image_view, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        

        Glide.with(mContext)
            .load(natureList[position].space)
            .into(holder.imageView)
    }

    override fun getItemCount(): Int {
        return natureList.size
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var imageView: ImageView = itemView.findViewById(R.id.NatureView)

    }

    companion object {
        private const val Tag = "RecyclerView"
    }
}

Data list class

   class Nature (var space: String? = null) {

}

Update

First error

e: C:\Users\Khumo Kwezi Mashapa\AndroidStudioProjects\MyNotepad\app\src\main\java\com\khumomashapa\notes\fragments\NatureWallpapers.kt: (65, 17): Unresolved reference: viewModel

Second error

e: C:\Users\Khumo Kwezi Mashapa\AndroidStudioProjects\MyNotepad\app\src\main\java\com\khumomashapa\notes\fragments\NatureWallpapers.kt: (120, 93): No value passed for parameter 'downloadImage'
Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • Are you `Khumo Mashapa` or what, he was asking the same question few hours ago. – Mohamed Rejeb Jun 18 '22 at 21:23
  • That's my brother. We're working on a similar project. I didn't know he asked a similar question earlier. – Kwezi Mashapa Jun 18 '22 at 21:47
  • 1
    Oh that's ok, try my answer, I hope it will help you to achieve what you want – Mohamed Rejeb Jun 18 '22 at 21:48
  • The code and errors appear unrelated (except the Glide call) and the answer doesn't use internal cache. Add `.diskCacheStrategy(DiskCacheStrategy.SOURCE)` or `.downloadOnly()` to the Glide call. – Martin Zeitler Jun 18 '22 at 22:48
  • Does this answer your question? [Android Glide: How to download and cache bitmaps?](https://stackoverflow.com/questions/27640307/android-glide-how-to-download-and-cache-bitmaps) – Martin Zeitler Jun 18 '22 at 22:52
  • Thanks Martin Zeitler. They almost have what I'm looking for, but I don't see how I could download the glide images to internal storage with those solutions – Kwezi Mashapa Jun 18 '22 at 23:56

2 Answers2

2
  1. Add Okhttp dependency

First of all you need to have okhttp dependency on your app gradle:

implementation("com.squareup.okhttp3:okhttp:4.10.0")

Note: if you are having retrofit no need to add okhttp dependency

  1. Create download function

Ok now we are going to add the download logic in your view model, add okhttp instance declaration and download function to your view model:

Note: you can move download logic to a repository or anywhere you want, it's up to you.

class YourViewModel : ViewModel() {

    // add okhttp instance to your view model or you inject it with hilt if your using dependency injection
    private val okHttpClient = OkHttpClient.Builder().build()

    // add this function to your view model
    fun downloadImage(imageUrl: String) {
        val request = Request.Builder()
            .url(imageUrl)
            .build()

        okHttpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                // Download Failed, you can show error to the user
            }

            override fun onResponse(call: Call, response: Response) {
                if (!response.isSuccessful) {
                    // Download Failed, you can show error to the user
                    return
                }

                response.body?.let { responseBody ->
                    try {
                        // Convert response body to byte array
                        val imageByteArray = responseBody.byteStream().readBytes()

                        // Split image url so we can get the image name
                        val words = imageUrl.split("/").toTypedArray()

                        // Get the image name
                        val imageName = words.last()

                        // Init pathName (Downloads Directory)
                        val pathName = "${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_DOWNLOADS}"

                        // Create New file for the image
                        val file = File(pathName, imageName)

                        // Set byteArray To Image File
                        file.writeBytes(imageByteArray)
                    } catch(e: IOException) {
                        // Saving Image Failed, you can show error to the user
                        e.printStackTrace()
                    }
                }
            }
        })
    }

}
  1. Pass download function from your fragment to your adapter

Now you need to pass your download function to your adapter inside a lambda function, adapter creation in your fragment should look like this:

val adapter = AbstractAdapter(
    context = requireContext(),
    natureList = natureList, // your list here
    downloadImage = { imageUrl ->
        viewModel.downloadImage(imageUrl)
    }
)

Your adapter constructor will look like this:

class AbstractAdapter(
    private val mContext: Context, 
    private val natureList: ArrayList<Nature>,
    private val downloadImage: (imageUrl: String) -> Unit
): RecyclerView.Adapter<AbstractAdapter.ViewHolder>()
  1. Call downloadImage lambda inside downloadBtn click listener

now we will add downloadImage call inside the click listener

holder.downloadBtn.setOnClickListener {
    val imageUrl = natureList[position].space
    imageUrl?.let {
        downloadImage(it)
    }
}
  1. Add write external storage permission to AndroidManifest.xml

add this permission to your AndroidManifest.xml file to be able to add files to phone storage

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />

Note: This permission is only required for sqk version <= 28 that's why we added android:maxSdkVersion="28"

I hope that everything is clear. This code should give you the result that you want, try it and tell me if there is anything wrong.

Mohamed Rejeb
  • 2,281
  • 1
  • 7
  • 16
1

You can also use Download Manager bruv. Just create an OnItemClick interface and simply implement this function in your main fragment or class.

override fun onItemClick(item: String, pos:Int) {
    abstractData = item
    positionItem = pos

    if (checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED ){
       requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQ_CODE)

    }else{

    startDownloading()
    }

    Toast.makeText(requireActivity(), "Saved to Internal storage/Pictures/AbstractWallpaper", Toast.LENGTH_LONG).show()

}

private fun startDownloading() {

    val request = DownloadManager.Request(Uri.parse(abstractData))
    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
    request.setTitle("Abstract Wallpaper")
    request.setDescription("Your image is downloading")
    request.allowScanningByMediaScanner()
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, "AbstractWallpapers")
    val manager = activity?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    manager.enqueue(request)

    Toast.makeText(requireActivity(), "Download is starting...", Toast.LENGTH_LONG).show()
}
Khumo Mashapa
  • 390
  • 1
  • 4
  • 13