Search code examples
firebasekotlinfirebase-realtime-databaseandroid-recyclerviewandroid-download-manager

Download Multiple images from Firebase Realtime Database to device storage


I have a recyclerview that displays multiple images from Firebase Realtime Database. The recyclerview also has a button within it. I want this button to allow users to be able to download these images ONE AT A TIME once they click it.

Screenshot of app layout

Once users click "download" I want the images to be saved to their device storage. I've tried multiple solutions for this, but they weren't helpful as they were for either Firestore Database or only allowed for one image to be downloaded.

Code

class AbstractAdapter(private val mContext: Context, private val abstractList: List<Abstract>) :
RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
    return ViewHolder(view)
}

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


    holder.download_btn.setOnClickListener {
        downloadFile()


    }

    Glide.with(mContext)
        .load(abstractList[position].abstract)
        .into(holder.imageView)
}

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

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
    val download_btn: Button = itemView.findViewById(R.id.abstractDownloadBtn)

}

private fun downloadFile() {
    val storage = FirebaseStorage.getInstance()
    val storageRef = storage.getReferenceFromUrl("https://notes-72413.firebaseio.com/")
    val islandRef = storageRef.child("Abstract")
    val rootPath = File(Environment.getExternalStorageDirectory(), "abstract")
    if (!rootPath.exists()) {
        rootPath.mkdirs()
    }
    val localFile = File(rootPath, "imageName.jpeg")
    islandRef.getFile(localFile)
        .addOnSuccessListener(OnSuccessListener<FileDownloadTask.TaskSnapshot?> {
            Log.e("firebase ", ";local tem file created  created $localFile")
            //  updateDb(timestamp,localFile.toString(),position);
        }).addOnFailureListener(OnFailureListener { exception ->
            Log.e(
                "firebase ",
                ";local tem file not created  created $exception"
            )
        })
}


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

I've tried this code, but once I click the "download" button it immediately crashes and Logcat says Firebase Storage URLs must point to an object in your Storage Bucket. Please obtain a URL using the Firebase Console or getDownloadUrl()

My Firebase Realtime Database

Realtime database

There's 64 files in total

Summary

I have a recyclerview that displays images from Firebase Realtime Database. Once users click the "download" button, it only downloads one image at a time to their device storage.

Update

private fun downloadFile() {
    val storage = FirebaseStorage.getInstance()
    val storageRef = storage.getReferenceFromUrl("abstract")

    val rootPath = File(Environment.getExternalStorageDirectory(), "abstract")
    if (!rootPath.exists()) {
        rootPath.mkdirs()
    }

    val localFile = File(rootPath, "imageName.jpeg")
    storageRef.child("Abstract").downloadUrl.addOnSuccessListener { Log.e("firebase ", ";local tem file created  created $localFile")

    }.addOnFailureListener(OnFailureListener { exception ->
            Log.e("firebase ", ";local tem file not created  created $exception")
        })
}

These are the changes I made to my downloadFile function, but I still get an error:

The storage Uri could not be parsed

Second update

2022-06-11 21:36:00.536 29751-29751/com.khumomashapa.notes E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.khumomashapa.notes, PID: 29751
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:523)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
 Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055) 
 Caused by: java.net.MalformedURLException: no protocol: 
    at java.net.URL.<init>(URL.java:601)
    at java.net.URL.<init>(URL.java:498)
    at java.net.URL.<init>(URL.java:447)
    at com.khumomashapa.notes.adapter.AbstractAdapter.downloadFile(AbstractAdapter.kt:57)
    at com.khumomashapa.notes.adapter.AbstractAdapter.onBindViewHolder$lambda-0(AbstractAdapter.kt:35)
    at com.khumomashapa.notes.adapter.AbstractAdapter.$r8$lambda$Rrmx0DFlwJu1z6QtjG8WCQp6NQQ(Unknown Source:0)
    at com.khumomashapa.notes.adapter.AbstractAdapter$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
    at android.view.View.performClick(View.java:7216)
    at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1217)
    at android.view.View.performClickInternal(View.java:7190)
    at android.view.View.access$3500(View.java:827)
    at android.view.View$PerformClick.run(View.java:27663)
    at android.os.Handler.handleCallback(Handler.java:900)
    at android.os.Handler.dispatchMessage(Handler.java:103)
    at android.os.Looper.loop(Looper.java:219)
    at android.app.ActivityThread.main(ActivityThread.java:8349)
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055) 

Code

class AbstractAdapter(private val mContext: Context, private val abstractList: List<Abstract>) :
RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
    return ViewHolder(view)
}

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


    holder.download_btn.setOnClickListener {
        downloadFile(url = String(), file = String.toString())

    }

    Glide.with(mContext)
        .load(abstractList[position].abstract)
        .into(holder.imageView)
}

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

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
    val download_btn: Button = itemView.findViewById(R.id.abstractDownloadBtn)

}

@Throws(IOException::class)

private fun downloadFile(url: String, file: String) {
    val urlObj = URL(url)
    val fileObj = File(file)
    val conn = urlObj.openConnection()
    val buffer = ByteArray(1024)
    object : BufferedOutputStream(FileOutputStream(fileObj)) {
        var `in` = BufferedInputStream(conn.getInputStream())
        init {
            var read: Int
            while (`in`.read(buffer, 0, buffer.size) >= 0.also { read = it });
            run {
                out.write(buffer, 0, read)
            }
            out.flush()
        }
    }.use { out -> }
}

Solution

  • I found the perfect solution to my problem. All I had to do was create an OnItemClick interface to get a different result for each item click and use Download manager to download the images.

    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()
    }